diff options
Diffstat (limited to 'src/tools/rust-analyzer/crates')
243 files changed, 14344 insertions, 3491 deletions
diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index 11f7b068e..accb14a51 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -76,7 +76,7 @@ impl fmt::Display for FlycheckConfig { #[derive(Debug)] pub struct FlycheckHandle { // XXX: drop order is significant - sender: Sender<Restart>, + sender: Sender<StateChange>, _thread: jod_thread::JoinHandle, id: usize, } @@ -89,7 +89,7 @@ impl FlycheckHandle { workspace_root: AbsPathBuf, ) -> FlycheckHandle { let actor = FlycheckActor::new(id, sender, config, workspace_root); - let (sender, receiver) = unbounded::<Restart>(); + let (sender, receiver) = unbounded::<StateChange>(); let thread = jod_thread::Builder::new() .name("Flycheck".to_owned()) .spawn(move || actor.run(receiver)) @@ -99,12 +99,12 @@ impl FlycheckHandle { /// Schedule a re-start of the cargo check worker. pub fn restart(&self) { - self.sender.send(Restart::Yes).unwrap(); + self.sender.send(StateChange::Restart).unwrap(); } /// Stop this cargo check worker. pub fn cancel(&self) { - self.sender.send(Restart::No).unwrap(); + self.sender.send(StateChange::Cancel).unwrap(); } pub fn id(&self) -> usize { @@ -149,9 +149,9 @@ pub enum Progress { DidFailToRestart(String), } -enum Restart { - Yes, - No, +enum StateChange { + Restart, + Cancel, } /// A [`FlycheckActor`] is a single check instance of a workspace. @@ -172,7 +172,7 @@ struct FlycheckActor { } enum Event { - Restart(Restart), + RequestStateChange(StateChange), CheckEvent(Option<CargoMessage>), } @@ -191,30 +191,31 @@ impl FlycheckActor { self.send(Message::Progress { id: self.id, progress }); } - fn next_event(&self, inbox: &Receiver<Restart>) -> Option<Event> { + fn next_event(&self, inbox: &Receiver<StateChange>) -> Option<Event> { let check_chan = self.cargo_handle.as_ref().map(|cargo| &cargo.receiver); if let Ok(msg) = inbox.try_recv() { // give restarts a preference so check outputs don't block a restart or stop - return Some(Event::Restart(msg)); + return Some(Event::RequestStateChange(msg)); } select! { - recv(inbox) -> msg => msg.ok().map(Event::Restart), + recv(inbox) -> msg => msg.ok().map(Event::RequestStateChange), recv(check_chan.unwrap_or(&never())) -> msg => Some(Event::CheckEvent(msg.ok())), } } - fn run(mut self, inbox: Receiver<Restart>) { + fn run(mut self, inbox: Receiver<StateChange>) { 'event: while let Some(event) = self.next_event(&inbox) { match event { - Event::Restart(Restart::No) => { + Event::RequestStateChange(StateChange::Cancel) => { + tracing::debug!(flycheck_id = self.id, "flycheck cancelled"); self.cancel_check_process(); } - Event::Restart(Restart::Yes) => { + Event::RequestStateChange(StateChange::Restart) => { // Cancel the previously spawned process self.cancel_check_process(); while let Ok(restart) = inbox.recv_timeout(Duration::from_millis(50)) { // restart chained with a stop, so just cancel - if let Restart::No = restart { + if let StateChange::Cancel = restart { continue 'event; } } @@ -255,10 +256,20 @@ impl FlycheckActor { } Event::CheckEvent(Some(message)) => match message { CargoMessage::CompilerArtifact(msg) => { + tracing::trace!( + flycheck_id = self.id, + artifact = msg.target.name, + "artifact received" + ); self.report_progress(Progress::DidCheckCrate(msg.target.name)); } CargoMessage::Diagnostic(msg) => { + tracing::trace!( + flycheck_id = self.id, + message = msg.message, + "diagnostic received" + ); self.send(Message::AddDiagnostic { id: self.id, workspace_root: self.root.clone(), @@ -445,42 +456,56 @@ impl CargoActor { // simply skip a line if it doesn't parse, which just ignores any // erroneous output. - let mut error = String::new(); - let mut read_at_least_one_message = false; + let mut stdout_errors = String::new(); + let mut stderr_errors = String::new(); + let mut read_at_least_one_stdout_message = false; + let mut read_at_least_one_stderr_message = false; + let process_line = |line: &str, error: &mut String| { + // Try to deserialize a message from Cargo or Rustc. + let mut deserializer = serde_json::Deserializer::from_str(line); + deserializer.disable_recursion_limit(); + if let Ok(message) = JsonMessage::deserialize(&mut deserializer) { + match message { + // Skip certain kinds of messages to only spend time on what's useful + JsonMessage::Cargo(message) => match message { + cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => { + self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap(); + } + cargo_metadata::Message::CompilerMessage(msg) => { + self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap(); + } + _ => (), + }, + JsonMessage::Rustc(message) => { + self.sender.send(CargoMessage::Diagnostic(message)).unwrap(); + } + } + return true; + } + + error.push_str(line); + error.push('\n'); + return false; + }; let output = streaming_output( self.stdout, self.stderr, &mut |line| { - read_at_least_one_message = true; - - // Try to deserialize a message from Cargo or Rustc. - let mut deserializer = serde_json::Deserializer::from_str(line); - deserializer.disable_recursion_limit(); - if let Ok(message) = JsonMessage::deserialize(&mut deserializer) { - match message { - // Skip certain kinds of messages to only spend time on what's useful - JsonMessage::Cargo(message) => match message { - cargo_metadata::Message::CompilerArtifact(artifact) - if !artifact.fresh => - { - self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap(); - } - cargo_metadata::Message::CompilerMessage(msg) => { - self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap(); - } - _ => (), - }, - JsonMessage::Rustc(message) => { - self.sender.send(CargoMessage::Diagnostic(message)).unwrap(); - } - } + if process_line(line, &mut stdout_errors) { + read_at_least_one_stdout_message = true; } }, &mut |line| { - error.push_str(line); - error.push('\n'); + if process_line(line, &mut stderr_errors) { + read_at_least_one_stderr_message = true; + } }, ); + + let read_at_least_one_message = + read_at_least_one_stdout_message || read_at_least_one_stderr_message; + let mut error = stdout_errors; + error.push_str(&stderr_errors); match output { Ok(_) => Ok((read_at_least_one_message, error)), Err(e) => Err(io::Error::new(e.kind(), format!("{e:?}: {error}"))), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs index 9bc1c54a3..b336f59ff 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/adt.rs @@ -40,6 +40,7 @@ pub struct StructData { pub repr: Option<ReprOptions>, pub visibility: RawVisibility, pub rustc_has_incoherent_inherent_impls: bool, + pub fundamental: bool, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -173,10 +174,10 @@ impl StructData { let item_tree = loc.id.item_tree(db); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); - let rustc_has_incoherent_inherent_impls = item_tree - .attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()) - .by_key("rustc_has_incoherent_inherent_impls") - .exists(); + let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); + let rustc_has_incoherent_inherent_impls = + attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); + let fundamental = attrs.by_key("fundamental").exists(); let strukt = &item_tree[loc.id.value]; let (variant_data, diagnostics) = lower_fields( @@ -196,6 +197,7 @@ impl StructData { repr, visibility: item_tree[strukt.visibility].clone(), rustc_has_incoherent_inherent_impls, + fundamental, }), diagnostics.into(), ) @@ -215,10 +217,10 @@ impl StructData { let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); - let rustc_has_incoherent_inherent_impls = item_tree - .attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()) - .by_key("rustc_has_incoherent_inherent_impls") - .exists(); + let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); + let rustc_has_incoherent_inherent_impls = + attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); + let fundamental = attrs.by_key("fundamental").exists(); let union = &item_tree[loc.id.value]; let (variant_data, diagnostics) = lower_fields( @@ -238,6 +240,7 @@ impl StructData { repr, visibility: item_tree[union.visibility].clone(), rustc_has_incoherent_inherent_impls, + fundamental, }), diagnostics.into(), ) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs index fcd92ad33..200072c17 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/attr.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/attr.rs @@ -300,6 +300,7 @@ impl AttrsWithOwner { AdtId::UnionId(it) => attrs_from_item_tree(it.lookup(db).id, db), }, AttrDefId::TraitId(it) => attrs_from_item_tree(it.lookup(db).id, db), + AttrDefId::TraitAliasId(it) => attrs_from_item_tree(it.lookup(db).id, db), AttrDefId::MacroId(it) => match it { MacroId::Macro2Id(it) => attrs_from_item_tree(it.lookup(db).id, db), MacroId::MacroRulesId(it) => attrs_from_item_tree(it.lookup(db).id, db), @@ -315,26 +316,14 @@ impl AttrsWithOwner { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( db.upcast(), - src.with_value(src.value[it.local_id()].as_ref().either( - |it| match it { - ast::TypeOrConstParam::Type(it) => it as _, - ast::TypeOrConstParam::Const(it) => it as _, - }, - |it| it as _, - )), + src.with_value(&src.value[it.local_id()]), ) } GenericParamId::TypeParamId(it) => { let src = it.parent().child_source(db); RawAttrs::from_attrs_owner( db.upcast(), - src.with_value(src.value[it.local_id()].as_ref().either( - |it| match it { - ast::TypeOrConstParam::Type(it) => it as _, - ast::TypeOrConstParam::Const(it) => it as _, - }, - |it| it as _, - )), + src.with_value(&src.value[it.local_id()]), ) } GenericParamId::LifetimeParamId(it) => { @@ -404,6 +393,7 @@ impl AttrsWithOwner { AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), + AttrDefId::TraitAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::MacroId(id) => match id { MacroId::Macro2Id(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), @@ -412,28 +402,14 @@ impl AttrsWithOwner { }, AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AnyHasAttrs::new), AttrDefId::GenericParamId(id) => match id { - GenericParamId::ConstParamId(id) => { - id.parent().child_source(db).map(|source| match &source[id.local_id()] { - Either::Left(ast::TypeOrConstParam::Type(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Left(ast::TypeOrConstParam::Const(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), - }) - } - GenericParamId::TypeParamId(id) => { - id.parent().child_source(db).map(|source| match &source[id.local_id()] { - Either::Left(ast::TypeOrConstParam::Type(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Left(ast::TypeOrConstParam::Const(id)) => { - ast::AnyHasAttrs::new(id.clone()) - } - Either::Right(id) => ast::AnyHasAttrs::new(id.clone()), - }) - } + GenericParamId::ConstParamId(id) => id + .parent() + .child_source(db) + .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), + GenericParamId::TypeParamId(id) => id + .parent() + .child_source(db) + .map(|source| ast::AnyHasAttrs::new(source[id.local_id()].clone())), GenericParamId::LifetimeParamId(id) => id .parent .child_source(db) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index 8fd9255b8..b70e658ef 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -24,7 +24,9 @@ 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, RecordFieldPat, + }, item_scope::BuiltinShadowMode, macro_id_to_def_id, nameres::DefMap, @@ -270,7 +272,7 @@ pub struct Mark { pub struct Body { pub exprs: Arena<Expr>, pub pats: Arena<Pat>, - pub or_pats: FxHashMap<PatId, Arc<[PatId]>>, + pub bindings: Arena<Binding>, pub labels: Arena<Label>, /// The patterns for the function's parameters. While the parameter types are /// part of the function signature, the patterns are not (they don't change @@ -409,18 +411,6 @@ impl Body { .map(move |&block| (block, db.block_def_map(block).expect("block ID without DefMap"))) } - pub fn pattern_representative(&self, pat: PatId) -> PatId { - self.or_pats.get(&pat).and_then(|pats| pats.first().copied()).unwrap_or(pat) - } - - /// Retrieves all ident patterns this pattern shares the ident with. - pub fn ident_patterns_for<'slf>(&'slf self, pat: &'slf PatId) -> &'slf [PatId] { - match self.or_pats.get(pat) { - Some(pats) => pats, - None => std::slice::from_ref(pat), - } - } - pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String { pretty::print_body_hir(db, self, owner) } @@ -435,13 +425,52 @@ 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, 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(); + } + + pub fn walk_bindings_in_pat(&self, pat_id: PatId, mut f: impl FnMut(BindingId)) { + self.walk_pats(pat_id, &mut |pat| { + if let Pat::Bind { id, .. } = pat { + f(*id); + } + }); + } + + pub fn walk_pats(&self, pat_id: PatId, f: &mut impl FnMut(&Pat)) { + let pat = &self[pat_id]; + f(pat); + match pat { + Pat::Range { .. } + | Pat::Lit(..) + | Pat::Path(..) + | Pat::ConstBlock(..) + | Pat::Wild + | Pat::Missing => {} + &Pat::Bind { subpat, .. } => { + if let Some(subpat) = subpat { + self.walk_pats(subpat, f); + } + } + Pat::Or(args) | Pat::Tuple { args, .. } | Pat::TupleStruct { args, .. } => { + args.iter().copied().for_each(|p| self.walk_pats(p, f)); + } + Pat::Ref { pat, .. } => self.walk_pats(*pat, f), + Pat::Slice { prefix, slice, suffix } => { + let total_iter = prefix.iter().chain(slice.iter()).chain(suffix.iter()); + total_iter.copied().for_each(|p| self.walk_pats(p, f)); + } + Pat::Record { args, .. } => { + args.iter().for_each(|RecordFieldPat { pat, .. }| self.walk_pats(*pat, f)); + } + Pat::Box { inner } => self.walk_pats(*inner, f), + } } } @@ -451,7 +480,7 @@ impl Default for Body { body_expr: dummy_expr_id(), exprs: Default::default(), pats: Default::default(), - or_pats: Default::default(), + bindings: Default::default(), labels: Default::default(), params: Default::default(), block_scopes: Default::default(), @@ -484,6 +513,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/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs index 04b1c4f01..fedaf3955 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs +++ b/src/tools/rust-analyzer/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,14 +31,14 @@ 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}, type_ref::{Mutability, Rawness, TypeRef}, - AdtId, BlockLoc, ModuleDefId, UnresolvedMacro, + AdtId, BlockId, BlockLoc, ModuleDefId, UnresolvedMacro, }; pub struct LowerCtx<'a> { @@ -87,16 +88,14 @@ 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(), block_scopes: Vec::new(), _c: Count::new(), - or_pats: Default::default(), }, expander, - name_to_pat_grouping: Default::default(), - is_lowering_inside_or_pat: false, is_lowering_assignee_expr: false, is_lowering_generator: false, } @@ -109,13 +108,26 @@ struct ExprCollector<'a> { ast_id_map: Arc<AstIdMap>, body: Body, source_map: BodySourceMap, - // a poor-mans union-find? - name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>, - is_lowering_inside_or_pat: bool, is_lowering_assignee_expr: bool, is_lowering_generator: bool, } +#[derive(Debug, Default)] +struct BindingList { + map: FxHashMap<Name, BindingId>, +} + +impl BindingList { + fn find( + &mut self, + ec: &mut ExprCollector<'_>, + name: Name, + mode: BindingAnnotation, + ) -> BindingId { + *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode)) + } +} + impl ExprCollector<'_> { fn collect( mut self, @@ -127,17 +139,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 +190,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()); @@ -238,33 +252,32 @@ impl ExprCollector<'_> { } ast::Expr::BlockExpr(e) => match e.modifier() { Some(ast::BlockModifier::Try(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::TryBlock { + id, + statements, + tail, + }) } Some(ast::BlockModifier::Unsafe(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Unsafe { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::Unsafe { + id, + statements, + tail, + }) } - // FIXME: we need to record these effects somewhere... Some(ast::BlockModifier::Label(label)) => { let label = self.collect_label(label); - let res = self.collect_block(e); - match &mut self.body.exprs[res] { - Expr::Block { label: block_label, .. } => { - *block_label = Some(label); - } - _ => unreachable!(), - } - res - } - Some(ast::BlockModifier::Async(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Async { body }, syntax_ptr) - } - Some(ast::BlockModifier::Const(_)) => { - let body = self.collect_block(e); - self.alloc_expr(Expr::Const { body }, syntax_ptr) + self.collect_block_(e, |id, statements, tail| Expr::Block { + id, + statements, + tail, + label: Some(label), + }) } + Some(ast::BlockModifier::Async(_)) => self + .collect_block_(e, |id, statements, tail| Expr::Async { id, statements, tail }), + Some(ast::BlockModifier::Const(_)) => self + .collect_block_(e, |id, statements, tail| Expr::Const { id, statements, tail }), None => self.collect_block(e), }, ast::Expr::LoopExpr(e) => { @@ -486,6 +499,8 @@ impl ExprCollector<'_> { Movability::Movable }; ClosureKind::Generator(movability) + } else if e.async_token().is_some() { + ClosureKind::Async } else { ClosureKind::Closure }; @@ -737,6 +752,19 @@ impl ExprCollector<'_> { } fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId { + self.collect_block_(block, |id, statements, tail| Expr::Block { + id, + statements, + tail, + label: None, + }) + } + + fn collect_block_( + &mut self, + block: ast::BlockExpr, + mk_block: impl FnOnce(BlockId, Box<[Statement]>, Option<ExprId>) -> Expr, + ) -> ExprId { let file_local_id = self.ast_id_map.ast_id(&block); let ast_id = AstId::new(self.expander.current_file_id, file_local_id); let block_loc = @@ -769,15 +797,8 @@ impl ExprCollector<'_> { }); let syntax_node_ptr = AstPtr::new(&block.into()); - let expr_id = self.alloc_expr( - Expr::Block { - id: block_id, - statements: statements.into_boxed_slice(), - tail, - label: None, - }, - syntax_node_ptr, - ); + let expr_id = self + .alloc_expr(mk_block(block_id, statements.into_boxed_slice(), tail), syntax_node_ptr); self.expander.def_map = prev_def_map; self.expander.module = prev_local_module; @@ -799,13 +820,7 @@ impl ExprCollector<'_> { } fn collect_pat(&mut self, pat: ast::Pat) -> PatId { - let pat_id = self.collect_pat_(pat); - 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()))); - } - self.is_lowering_inside_or_pat = false; - pat_id + self.collect_pat_(pat, &mut BindingList::default()) } fn collect_pat_opt(&mut self, pat: Option<ast::Pat>) -> PatId { @@ -815,16 +830,18 @@ 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); - 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 is_simple_ident_pat = + annotation == BindingAnnotation::Unannotated && subpat.is_none(); + let (binding, pattern) = if is_simple_ident_pat { // 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( @@ -834,12 +851,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 => @@ -847,30 +864,34 @@ 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(key) = key { - self.name_to_pat_grouping.entry(key).or_default().push(pat); + if let Some(binding_id) = binding { + self.add_definition_to_binding(binding_id, pat); } return pat; } 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 } } @@ -880,13 +901,12 @@ impl ExprCollector<'_> { path.map(Pat::Path).unwrap_or(Pat::Missing) } 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, @@ -899,7 +919,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 }) }) @@ -918,9 +938,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) => { @@ -943,7 +969,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) => { @@ -960,7 +986,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; @@ -974,21 +1000,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) @@ -1017,6 +1047,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/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs index 4b4664a1c..5a9b825a2 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/pretty.rs +++ b/src/tools/rust-analyzer/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, }; @@ -292,18 +292,6 @@ impl<'a> Printer<'a> { self.print_expr(*expr); w!(self, "?"); } - Expr::TryBlock { body } => { - w!(self, "try "); - self.print_expr(*body); - } - Expr::Async { body } => { - w!(self, "async "); - self.print_expr(*body); - } - Expr::Const { body } => { - w!(self, "const "); - self.print_expr(*body); - } Expr::Cast { expr, type_ref } => { self.print_expr(*expr); w!(self, " as "); @@ -372,8 +360,14 @@ impl<'a> Printer<'a> { w!(self, "]"); } Expr::Closure { args, arg_types, ret_type, body, closure_kind } => { - if let ClosureKind::Generator(Movability::Static) = closure_kind { - w!(self, "static "); + match closure_kind { + ClosureKind::Generator(Movability::Static) => { + w!(self, "static "); + } + ClosureKind::Async => { + w!(self, "async "); + } + _ => (), } w!(self, "|"); for (i, (pat, ty)) in args.iter().zip(arg_types.iter()).enumerate() { @@ -402,10 +396,6 @@ impl<'a> Printer<'a> { } w!(self, ")"); } - Expr::Unsafe { body } => { - w!(self, "unsafe "); - self.print_expr(*body); - } Expr::Array(arr) => { w!(self, "["); if !matches!(arr, Array::ElementList { elements, .. } if elements.is_empty()) { @@ -428,25 +418,47 @@ impl<'a> Printer<'a> { } Expr::Literal(lit) => self.print_literal(lit), Expr::Block { id: _, statements, tail, label } => { - self.whitespace(); - if let Some(lbl) = label { - w!(self, "{}: ", self.body[*lbl].name); + let label = label.map(|lbl| format!("{}: ", self.body[lbl].name)); + self.print_block(label.as_deref(), statements, tail); + } + Expr::Unsafe { id: _, statements, tail } => { + self.print_block(Some("unsafe "), statements, tail); + } + Expr::TryBlock { id: _, statements, tail } => { + self.print_block(Some("try "), statements, tail); + } + Expr::Async { id: _, statements, tail } => { + self.print_block(Some("async "), statements, tail); + } + Expr::Const { id: _, statements, tail } => { + self.print_block(Some("const "), statements, tail); + } + } + } + + fn print_block( + &mut self, + label: Option<&str>, + statements: &Box<[Statement]>, + tail: &Option<la_arena::Idx<Expr>>, + ) { + self.whitespace(); + if let Some(lbl) = label { + w!(self, "{}", lbl); + } + w!(self, "{{"); + if !statements.is_empty() || tail.is_some() { + self.indented(|p| { + for stmt in &**statements { + p.print_stmt(stmt); } - w!(self, "{{"); - if !statements.is_empty() || tail.is_some() { - self.indented(|p| { - for stmt in &**statements { - p.print_stmt(stmt); - } - if let Some(tail) = tail { - p.print_expr(*tail); - } - p.newline(); - }); + if let Some(tail) = tail { + p.print_expr(*tail); } - w!(self, "}}"); - } + p.newline(); + }); } + w!(self, "}}"); } fn print_pat(&mut self, pat: PatId) { @@ -518,14 +530,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); @@ -629,4 +635,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/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs index 2617d4288..12fc1f116 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/scope.rs +++ b/src/tools/rust-analyzer/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 } } @@ -66,6 +66,7 @@ impl ExprScopes { self.scopes[scope].label.clone() } + /// Returns the scopes in ascending order. pub fn scope_chain(&self, scope: Option<ScopeId>) -> impl Iterator<Item = ScopeId> + '_ { std::iter::successors(scope, move |&scope| self.scopes[scope].parent) } @@ -125,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) { @@ -169,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); @@ -194,10 +200,20 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope scopes.set_scope(expr, scope); compute_block_scopes(statements, *tail, body, scopes, &mut scope); } + Expr::Unsafe { id, statements, tail } + | Expr::Async { id, statements, tail } + | Expr::Const { id, statements, tail } + | Expr::TryBlock { id, statements, tail } => { + let mut scope = scopes.new_block_scope(*scope, *id, None); + // Overwrite the old scope for the block expr, so that every block scope can be found + // via the block itself (important for blocks that only contain items, no expressions). + scopes.set_scope(expr, scope); + compute_block_scopes(statements, *tail, body, scopes, &mut 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 } => { @@ -218,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); @@ -237,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)), }; @@ -439,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(); @@ -449,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/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs index 3bba08cfc..77ac221e5 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/tests/block.rs @@ -395,3 +395,25 @@ fn foo() { "#]], ) } + +#[test] +fn trailing_expr_macro_expands_stmts() { + check_at( + r#" +macro_rules! foo { + () => { const FOO: u32 = 0;const BAR: u32 = 0; }; +} +fn f() {$0 + foo!{} +}; + "#, + expect![[r#" + block scope + BAR: v + FOO: v + + crate + f: v + "#]], + ) +} diff --git a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs index 19d2fe956..68b57acca 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/child_by_source.rs @@ -1,7 +1,7 @@ //! When *constructing* `hir`, we start at some parent syntax node and recursively //! lower the children. //! -//! This modules allows one to go in the opposite direction: start with a syntax +//! This module allows one to go in the opposite direction: start with a syntax //! node for a *child*, and get its hir. use either::Either; @@ -145,6 +145,7 @@ impl ChildBySource for ItemScope { ModuleDefId::StaticId(id) => insert!(map[keys::STATIC].insert(id)), ModuleDefId::TypeAliasId(id) => insert!(map[keys::TYPE_ALIAS].insert(id)), ModuleDefId::TraitId(id) => insert!(map[keys::TRAIT].insert(id)), + ModuleDefId::TraitAliasId(id) => insert!(map[keys::TRAIT_ALIAS].insert(id)), ModuleDefId::AdtId(adt) => match adt { AdtId::StructId(id) => insert!(map[keys::STRUCT].insert(id)), AdtId::UnionId(id) => insert!(map[keys::UNION].insert(id)), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index c3c1dfd39..1633a33be 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -22,7 +22,7 @@ use crate::{ visibility::RawVisibility, AssocItemId, AstIdWithPath, ConstId, ConstLoc, FunctionId, FunctionLoc, HasModule, ImplId, Intern, ItemContainerId, ItemLoc, Lookup, Macro2Id, MacroRulesId, ModuleId, ProcMacroId, - StaticId, TraitId, TypeAliasId, TypeAliasLoc, + StaticId, TraitAliasId, TraitId, TypeAliasId, TypeAliasLoc, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -35,6 +35,7 @@ pub struct FunctionData { pub visibility: RawVisibility, pub abi: Option<Interned<str>>, pub legacy_const_generics_indices: Box<[u32]>, + pub rustc_allow_incoherent_impl: bool, flags: FnFlags, } @@ -84,13 +85,14 @@ impl FunctionData { } } - let legacy_const_generics_indices = item_tree - .attrs(db, krate, ModItem::from(loc.id.value).into()) + let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into()); + let legacy_const_generics_indices = attrs .by_key("rustc_legacy_const_generics") .tt_values() .next() .map(parse_rustc_legacy_const_generics) .unwrap_or_default(); + let rustc_allow_incoherent_impl = attrs.by_key("rustc_allow_incoherent_impl").exists(); Arc::new(FunctionData { name: func.name.clone(), @@ -108,6 +110,7 @@ impl FunctionData { abi: func.abi.clone(), legacy_const_generics_indices, flags, + rustc_allow_incoherent_impl, }) } @@ -171,6 +174,7 @@ pub struct TypeAliasData { pub visibility: RawVisibility, pub is_extern: bool, pub rustc_has_incoherent_inherent_impls: bool, + pub rustc_allow_incoherent_impl: bool, /// Bounds restricting the type alias itself (eg. `type Ty: Bound;` in a trait or impl). pub bounds: Vec<Interned<TypeBound>>, } @@ -189,10 +193,14 @@ impl TypeAliasData { item_tree[typ.visibility].clone() }; - let rustc_has_incoherent_inherent_impls = item_tree - .attrs(db, loc.container.module(db).krate(), ModItem::from(loc.id.value).into()) - .by_key("rustc_has_incoherent_inherent_impls") - .exists(); + let attrs = item_tree.attrs( + db, + loc.container.module(db).krate(), + ModItem::from(loc.id.value).into(), + ); + let rustc_has_incoherent_inherent_impls = + attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); + let rustc_allow_incoherent_impl = attrs.by_key("rustc_allow_incoherent_impl").exists(); Arc::new(TypeAliasData { name: typ.name.clone(), @@ -200,6 +208,7 @@ impl TypeAliasData { visibility, is_extern: matches!(loc.container, ItemContainerId::ExternBlockId(_)), rustc_has_incoherent_inherent_impls, + rustc_allow_incoherent_impl, bounds: typ.bounds.to_vec(), }) } @@ -212,11 +221,12 @@ pub struct TraitData { pub is_auto: bool, pub is_unsafe: bool, pub rustc_has_incoherent_inherent_impls: bool, + pub skip_array_during_method_dispatch: bool, + pub fundamental: bool, pub visibility: RawVisibility, /// Whether the trait has `#[rust_skip_array_during_method_dispatch]`. `hir_ty` will ignore /// method calls to this trait's methods when the receiver is an array and the crate edition is /// 2015 or 2018. - pub skip_array_during_method_dispatch: bool, // box it as the vec is usually empty anyways pub attribute_calls: Option<Box<Vec<(AstId<ast::Item>, MacroCallId)>>>, } @@ -245,19 +255,12 @@ impl TraitData { attrs.by_key("rustc_skip_array_during_method_dispatch").exists(); let rustc_has_incoherent_inherent_impls = attrs.by_key("rustc_has_incoherent_inherent_impls").exists(); - let (items, attribute_calls, diagnostics) = match &tr_def.items { - Some(items) => { - let mut collector = AssocItemCollector::new( - db, - module_id, - tree_id.file_id(), - ItemContainerId::TraitId(tr), - ); - collector.collect(&item_tree, tree_id.tree_id(), items); - collector.finish() - } - None => Default::default(), - }; + let fundamental = attrs.by_key("fundamental").exists(); + let mut collector = + AssocItemCollector::new(db, module_id, tree_id.file_id(), ItemContainerId::TraitId(tr)); + collector.collect(&item_tree, tree_id.tree_id(), &tr_def.items); + let (items, attribute_calls, diagnostics) = collector.finish(); + ( Arc::new(TraitData { name, @@ -268,6 +271,7 @@ impl TraitData { visibility, skip_array_during_method_dispatch, rustc_has_incoherent_inherent_impls, + fundamental, }), diagnostics.into(), ) @@ -300,6 +304,23 @@ impl TraitData { } #[derive(Debug, Clone, PartialEq, Eq)] +pub struct TraitAliasData { + pub name: Name, + pub visibility: RawVisibility, +} + +impl TraitAliasData { + pub(crate) fn trait_alias_query(db: &dyn DefDatabase, id: TraitAliasId) -> Arc<TraitAliasData> { + let loc = id.lookup(db); + let item_tree = loc.id.item_tree(db); + let alias = &item_tree[loc.id.value]; + let visibility = item_tree[alias.visibility].clone(); + + Arc::new(TraitAliasData { name: alias.name.clone(), visibility }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ImplData { pub target_trait: Option<Interned<TraitRef>>, pub self_ty: Interned<TypeRef>, @@ -441,6 +462,7 @@ pub struct ConstData { pub name: Option<Name>, pub type_ref: Interned<TypeRef>, pub visibility: RawVisibility, + pub rustc_allow_incoherent_impl: bool, } impl ConstData { @@ -454,10 +476,16 @@ impl ConstData { item_tree[konst.visibility].clone() }; + let rustc_allow_incoherent_impl = item_tree + .attrs(db, loc.container.module(db).krate(), ModItem::from(loc.id.value).into()) + .by_key("rustc_allow_incoherent_impl") + .exists(); + Arc::new(ConstData { name: konst.name.clone(), type_ref: konst.type_ref.clone(), visibility, + rustc_allow_incoherent_impl, }) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/db.rs b/src/tools/rust-analyzer/crates/hir-def/src/db.rs index b23427a73..9371fc14d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/db.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use base_db::{salsa, CrateId, SourceDatabase, Upcast}; use either::Either; -use hir_expand::{db::AstDatabase, HirFileId}; +use hir_expand::{db::ExpandDatabase, HirFileId}; use intern::Interned; use la_arena::ArenaMap; use syntax::{ast, AstPtr}; @@ -14,7 +14,7 @@ use crate::{ body::{scope::ExprScopes, Body, BodySourceMap}, data::{ ConstData, FunctionData, ImplData, Macro2Data, MacroRulesData, ProcMacroData, StaticData, - TraitData, TypeAliasData, + TraitAliasData, TraitData, TypeAliasData, }, generics::GenericParams, import_map::ImportMap, @@ -25,8 +25,8 @@ use crate::{ AttrDefId, BlockId, BlockLoc, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, ExternBlockId, ExternBlockLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, LocalEnumVariantId, LocalFieldId, Macro2Id, Macro2Loc, MacroRulesId, MacroRulesLoc, ProcMacroId, ProcMacroLoc, - StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, - UnionId, UnionLoc, VariantId, + StaticId, StaticLoc, StructId, StructLoc, TraitAliasId, TraitAliasLoc, TraitId, TraitLoc, + TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, VariantId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -46,6 +46,8 @@ pub trait InternDatabase: SourceDatabase { #[salsa::interned] fn intern_trait(&self, loc: TraitLoc) -> TraitId; #[salsa::interned] + fn intern_trait_alias(&self, loc: TraitAliasLoc) -> TraitAliasId; + #[salsa::interned] fn intern_type_alias(&self, loc: TypeAliasLoc) -> TypeAliasId; #[salsa::interned] fn intern_impl(&self, loc: ImplLoc) -> ImplId; @@ -62,7 +64,7 @@ pub trait InternDatabase: SourceDatabase { } #[salsa::query_group(DefDatabaseStorage)] -pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { +pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast<dyn ExpandDatabase> { #[salsa::input] fn enable_proc_attr_macros(&self) -> bool; @@ -125,6 +127,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast<dyn AstDatabase> { #[salsa::invoke(TraitData::trait_data_with_diagnostics_query)] fn trait_data_with_diagnostics(&self, tr: TraitId) -> (Arc<TraitData>, Arc<[DefDiagnostic]>); + #[salsa::invoke(TraitAliasData::trait_alias_query)] + fn trait_alias_data(&self, e: TraitAliasId) -> Arc<TraitAliasData>; + #[salsa::invoke(TypeAliasData::type_alias_data_query)] fn type_alias_data(&self, e: TypeAliasId) -> Arc<TypeAliasData>; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs b/src/tools/rust-analyzer/crates/hir-def/src/expr.rs index 48028b7c6..19fa6b254 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expr.rs +++ b/src/tools/rust-analyzer/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)) @@ -52,13 +55,21 @@ pub type LabelId = Idx<Label>; // We convert float values into bits and that's how we don't need to deal with f32 and f64. // For PartialEq, bits comparison should work, as ordering is not important // https://github.com/rust-lang/rust-analyzer/issues/12380#issuecomment-1137284360 -#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] pub struct FloatTypeWrapper(u64); impl FloatTypeWrapper { pub fn new(value: f64) -> Self { Self(value.to_bits()) } + + pub fn into_f64(self) -> f64 { + f64::from_bits(self.0) + } + + pub fn into_f32(self) -> f32 { + f64::from_bits(self.0) as f32 + } } impl fmt::Display for FloatTypeWrapper { @@ -101,6 +112,26 @@ pub enum Expr { tail: Option<ExprId>, label: Option<LabelId>, }, + TryBlock { + id: BlockId, + statements: Box<[Statement]>, + tail: Option<ExprId>, + }, + Async { + id: BlockId, + statements: Box<[Statement]>, + tail: Option<ExprId>, + }, + Const { + id: BlockId, + statements: Box<[Statement]>, + tail: Option<ExprId>, + }, + Unsafe { + id: BlockId, + statements: Box<[Statement]>, + tail: Option<ExprId>, + }, Loop { body: ExprId, label: Option<LabelId>, @@ -164,15 +195,6 @@ pub enum Expr { Try { expr: ExprId, }, - TryBlock { - body: ExprId, - }, - Async { - body: ExprId, - }, - Const { - body: ExprId, - }, Cast { expr: ExprId, type_ref: Interned<TypeRef>, @@ -214,9 +236,6 @@ pub enum Expr { exprs: Box<[ExprId]>, is_assignee_expr: bool, }, - Unsafe { - body: ExprId, - }, Array(Array), Literal(Literal), Underscore, @@ -226,6 +245,7 @@ pub enum Expr { pub enum ClosureKind { Closure, Generator(Movability), + Async, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -282,13 +302,20 @@ impl Expr { Expr::Let { expr, .. } => { f(*expr); } - Expr::Block { statements, tail, .. } => { + Expr::Block { statements, tail, .. } + | Expr::TryBlock { statements, tail, .. } + | Expr::Unsafe { statements, tail, .. } + | Expr::Async { statements, tail, .. } + | Expr::Const { statements, tail, .. } => { for stmt in statements.iter() { match stmt { - Statement::Let { initializer, .. } => { + Statement::Let { initializer, else_branch, .. } => { if let &Some(expr) = initializer { f(expr); } + if let &Some(expr) = else_branch { + f(expr); + } } Statement::Expr { expr: expression, .. } => f(*expression), } @@ -297,10 +324,6 @@ impl Expr { f(expr); } } - Expr::TryBlock { body } - | Expr::Unsafe { body } - | Expr::Async { body } - | Expr::Const { body } => f(*body), Expr::Loop { body, .. } => f(*body), Expr::While { condition, body, .. } => { f(*condition); @@ -415,6 +438,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, @@ -432,7 +462,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/src/tools/rust-analyzer/crates/hir-def/src/generics.rs b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs index b2ab0c30e..e4912fa8a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/generics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/generics.rs @@ -187,6 +187,7 @@ impl GenericParams { GenericDefId::AdtId(AdtId::EnumId(id)) => id_to_generics!(id), GenericDefId::AdtId(AdtId::UnionId(id)) => id_to_generics!(id), GenericDefId::TraitId(id) => id_to_generics!(id), + GenericDefId::TraitAliasId(id) => id_to_generics!(id), GenericDefId::TypeAliasId(id) => id_to_generics!(id), GenericDefId::ImplId(id) => id_to_generics!(id), GenericDefId::EnumVariantId(_) | GenericDefId::ConstId(_) => { @@ -207,12 +208,10 @@ impl GenericParams { pub(crate) fn fill_bounds( &mut self, lower_ctx: &LowerCtx<'_>, - node: &dyn ast::HasTypeBounds, + type_bounds: Option<ast::TypeBoundList>, target: Either<TypeRef, LifetimeRef>, ) { - for bound in - node.type_bound_list().iter().flat_map(|type_bound_list| type_bound_list.bounds()) - { + for bound in type_bounds.iter().flat_map(|type_bound_list| type_bound_list.bounds()) { self.add_where_predicate_from_bound(lower_ctx, bound, None, target.clone()); } } @@ -233,7 +232,11 @@ impl GenericParams { }; self.type_or_consts.alloc(param.into()); let type_ref = TypeRef::Path(name.into()); - self.fill_bounds(lower_ctx, &type_param, Either::Left(type_ref)); + self.fill_bounds( + lower_ctx, + type_param.type_bound_list(), + Either::Left(type_ref), + ); } ast::TypeOrConstParam::Const(const_param) => { let name = const_param.name().map_or_else(Name::missing, |it| it.as_name()); @@ -255,7 +258,11 @@ impl GenericParams { let param = LifetimeParamData { name: name.clone() }; self.lifetimes.alloc(param); let lifetime_ref = LifetimeRef::new_name(name); - self.fill_bounds(lower_ctx, &lifetime_param, Either::Right(lifetime_ref)); + self.fill_bounds( + lower_ctx, + lifetime_param.type_bound_list(), + Either::Right(lifetime_ref), + ); } } @@ -421,6 +428,10 @@ fn file_id_and_params_of( let src = it.lookup(db).source(db); (src.file_id, src.value.generic_param_list()) } + GenericDefId::TraitAliasId(it) => { + let src = it.lookup(db).source(db); + (src.file_id, src.value.generic_param_list()) + } GenericDefId::TypeAliasId(it) => { let src = it.lookup(db).source(db); (src.file_id, src.value.generic_param_list()) @@ -435,7 +446,7 @@ fn file_id_and_params_of( } impl HasChildSource<LocalTypeOrConstParamId> for GenericDefId { - type Value = Either<ast::TypeOrConstParam, ast::Trait>; + type Value = Either<ast::TypeOrConstParam, ast::TraitOrAlias>; fn child_source( &self, db: &dyn DefDatabase, @@ -447,11 +458,20 @@ impl HasChildSource<LocalTypeOrConstParamId> for GenericDefId { let mut params = ArenaMap::default(); - // For traits the first type index is `Self`, we need to add it before the other params. - if let GenericDefId::TraitId(id) = *self { - let trait_ref = id.lookup(db).source(db).value; - let idx = idx_iter.next().unwrap(); - params.insert(idx, Either::Right(trait_ref)); + // For traits and trait aliases the first type index is `Self`, we need to add it before + // the other params. + match *self { + GenericDefId::TraitId(id) => { + let trait_ref = id.lookup(db).source(db).value; + let idx = idx_iter.next().unwrap(); + params.insert(idx, Either::Right(ast::TraitOrAlias::Trait(trait_ref))); + } + GenericDefId::TraitAliasId(id) => { + let alias = id.lookup(db).source(db).value; + let idx = idx_iter.next().unwrap(); + params.insert(idx, Either::Right(ast::TraitOrAlias::TraitAlias(alias))); + } + _ => {} } if let Some(generic_params_list) = generic_params_list { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs index 1ce191942..4f1f6000d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/import_map.rs @@ -264,6 +264,7 @@ pub enum ImportKind { Const, Static, Trait, + TraitAlias, TypeAlias, BuiltinType, AssociatedItem, @@ -459,6 +460,7 @@ fn item_import_kind(item: ItemInNs) -> Option<ImportKind> { ModuleDefId::ConstId(_) => ImportKind::Const, ModuleDefId::StaticId(_) => ImportKind::Static, ModuleDefId::TraitId(_) => ImportKind::Trait, + ModuleDefId::TraitAliasId(_) => ImportKind::TraitAlias, ModuleDefId::TypeAliasId(_) => ImportKind::TypeAlias, ModuleDefId::BuiltinType(_) => ImportKind::BuiltinType, ModuleDefId::MacroId(_) => ImportKind::Macro, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs index 53a4173ff..991e44703 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_scope.rs @@ -431,6 +431,7 @@ impl PerNs { ModuleDefId::EnumVariantId(_) => PerNs::both(def, def, v), ModuleDefId::ConstId(_) | ModuleDefId::StaticId(_) => PerNs::values(def, v), ModuleDefId::TraitId(_) => PerNs::types(def, v), + ModuleDefId::TraitAliasId(_) => PerNs::types(def, v), ModuleDefId::TypeAliasId(_) => PerNs::types(def, v), ModuleDefId::BuiltinType(_) => PerNs::types(def, v), ModuleDefId::MacroId(mac) => PerNs::macros(mac, v), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs index 19d01630e..9da5b2d47 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree.rs @@ -204,6 +204,7 @@ impl ItemTree { consts, statics, traits, + trait_aliases, impls, type_aliases, mods, @@ -226,6 +227,7 @@ impl ItemTree { consts.shrink_to_fit(); statics.shrink_to_fit(); traits.shrink_to_fit(); + trait_aliases.shrink_to_fit(); impls.shrink_to_fit(); type_aliases.shrink_to_fit(); mods.shrink_to_fit(); @@ -276,6 +278,7 @@ struct ItemTreeData { consts: Arena<Const>, statics: Arena<Static>, traits: Arena<Trait>, + trait_aliases: Arena<TraitAlias>, impls: Arena<Impl>, type_aliases: Arena<TypeAlias>, mods: Arena<Mod>, @@ -496,6 +499,7 @@ mod_items! { Const in consts -> ast::Const, Static in statics -> ast::Static, Trait in traits -> ast::Trait, + TraitAlias in trait_aliases -> ast::TraitAlias, Impl in impls -> ast::Impl, TypeAlias in type_aliases -> ast::TypeAlias, Mod in mods -> ast::Module, @@ -672,12 +676,19 @@ pub struct Trait { pub generic_params: Interned<GenericParams>, pub is_auto: bool, pub is_unsafe: bool, - /// This is [`None`] if this Trait is a trait alias. - pub items: Option<Box<[AssocItem]>>, + pub items: Box<[AssocItem]>, pub ast_id: FileAstId<ast::Trait>, } #[derive(Debug, Clone, Eq, PartialEq)] +pub struct TraitAlias { + pub name: Name, + pub visibility: RawVisibilityId, + pub generic_params: Interned<GenericParams>, + pub ast_id: FileAstId<ast::TraitAlias>, +} + +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Impl { pub generic_params: Interned<GenericParams>, pub target_trait: Option<Interned<TraitRef>>, @@ -872,6 +883,7 @@ impl ModItem { | ModItem::Enum(_) | ModItem::Static(_) | ModItem::Trait(_) + | ModItem::TraitAlias(_) | ModItem::Impl(_) | ModItem::Mod(_) | ModItem::MacroRules(_) @@ -899,6 +911,7 @@ impl ModItem { ModItem::Const(it) => tree[it.index].ast_id().upcast(), ModItem::Static(it) => tree[it.index].ast_id().upcast(), ModItem::Trait(it) => tree[it.index].ast_id().upcast(), + ModItem::TraitAlias(it) => tree[it.index].ast_id().upcast(), ModItem::Impl(it) => tree[it.index].ast_id().upcast(), ModItem::TypeAlias(it) => tree[it.index].ast_id().upcast(), ModItem::Mod(it) => tree[it.index].ast_id().upcast(), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs index d4d3c5ef1..77b186f8e 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/lower.rs @@ -3,7 +3,7 @@ use std::{collections::hash_map::Entry, sync::Arc}; use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; -use syntax::ast::{self, HasModuleItem}; +use syntax::ast::{self, HasModuleItem, HasTypeBounds}; use crate::{ generics::{GenericParams, TypeParamData, TypeParamProvenance}, @@ -90,6 +90,13 @@ impl<'a> Ctx<'a> { _ => None, }) .collect(); + if let Some(ast::Expr::MacroExpr(expr)) = block.tail_expr() { + if let Some(call) = expr.macro_call() { + if let Some(mod_item) = self.lower_mod_item(&call.into()) { + self.tree.top_level.push(mod_item); + } + } + } self.tree } @@ -110,6 +117,7 @@ impl<'a> Ctx<'a> { ast::Item::Const(ast) => self.lower_const(ast).into(), ast::Item::Module(ast) => self.lower_module(ast)?.into(), ast::Item::Trait(ast) => self.lower_trait(ast)?.into(), + ast::Item::TraitAlias(ast) => self.lower_trait_alias(ast)?.into(), ast::Item::Impl(ast) => self.lower_impl(ast)?.into(), ast::Item::Use(ast) => self.lower_use(ast)?.into(), ast::Item::ExternCrate(ast) => self.lower_extern_crate(ast)?.into(), @@ -147,7 +155,7 @@ impl<'a> Ctx<'a> { fn lower_struct(&mut self, strukt: &ast::Struct) -> Option<FileItemTreeId<Struct>> { let visibility = self.lower_visibility(strukt); let name = strukt.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Struct, strukt); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, strukt); let fields = self.lower_fields(&strukt.kind()); let ast_id = self.source_ast_id_map.ast_id(strukt); let res = Struct { name, visibility, generic_params, fields, ast_id }; @@ -211,7 +219,7 @@ impl<'a> Ctx<'a> { fn lower_union(&mut self, union: &ast::Union) -> Option<FileItemTreeId<Union>> { let visibility = self.lower_visibility(union); let name = union.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Union, union); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, union); let fields = match union.record_field_list() { Some(record_field_list) => self.lower_fields(&StructKind::Record(record_field_list)), None => Fields::Record(IdxRange::new(self.next_field_idx()..self.next_field_idx())), @@ -224,7 +232,7 @@ impl<'a> Ctx<'a> { fn lower_enum(&mut self, enum_: &ast::Enum) -> Option<FileItemTreeId<Enum>> { let visibility = self.lower_visibility(enum_); let name = enum_.name()?.as_name(); - let generic_params = self.lower_generic_params(GenericsOwner::Enum, enum_); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, enum_); let variants = match &enum_.variant_list() { Some(variant_list) => self.lower_variants(variant_list), None => IdxRange::new(self.next_variant_idx()..self.next_variant_idx()), @@ -372,8 +380,7 @@ impl<'a> Ctx<'a> { ast_id, flags, }; - res.explicit_generic_params = - self.lower_generic_params(GenericsOwner::Function(&res), func); + res.explicit_generic_params = self.lower_generic_params(HasImplicitSelf::No, func); Some(id(self.data().functions.alloc(res))) } @@ -386,7 +393,7 @@ impl<'a> Ctx<'a> { let type_ref = type_alias.ty().map(|it| self.lower_type_ref(&it)); let visibility = self.lower_visibility(type_alias); let bounds = self.lower_type_bounds(type_alias); - let generic_params = self.lower_generic_params(GenericsOwner::TypeAlias, type_alias); + let generic_params = self.lower_generic_params(HasImplicitSelf::No, type_alias); let ast_id = self.source_ast_id_map.ast_id(type_alias); let res = TypeAlias { name, @@ -442,27 +449,49 @@ impl<'a> Ctx<'a> { fn lower_trait(&mut self, trait_def: &ast::Trait) -> Option<FileItemTreeId<Trait>> { let name = trait_def.name()?.as_name(); let visibility = self.lower_visibility(trait_def); - let generic_params = self.lower_generic_params(GenericsOwner::Trait(trait_def), trait_def); + let generic_params = + self.lower_generic_params(HasImplicitSelf::Yes(trait_def.type_bound_list()), trait_def); let is_auto = trait_def.auto_token().is_some(); let is_unsafe = trait_def.unsafe_token().is_some(); - let items = trait_def.assoc_item_list().map(|list| { - list.assoc_items() - .filter_map(|item| { - let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); - self.lower_assoc_item(&item).map(|item| { - self.add_attrs(ModItem::from(item).into(), attrs); - item - }) - }) - .collect() - }); let ast_id = self.source_ast_id_map.ast_id(trait_def); - let res = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id }; - Some(id(self.data().traits.alloc(res))) + + let items = trait_def + .assoc_item_list() + .into_iter() + .flat_map(|list| list.assoc_items()) + .filter_map(|item| { + let attrs = RawAttrs::new(self.db.upcast(), &item, self.hygiene()); + self.lower_assoc_item(&item).map(|item| { + self.add_attrs(ModItem::from(item).into(), attrs); + item + }) + }) + .collect(); + + let def = Trait { name, visibility, generic_params, is_auto, is_unsafe, items, ast_id }; + Some(id(self.data().traits.alloc(def))) + } + + fn lower_trait_alias( + &mut self, + trait_alias_def: &ast::TraitAlias, + ) -> Option<FileItemTreeId<TraitAlias>> { + let name = trait_alias_def.name()?.as_name(); + let visibility = self.lower_visibility(trait_alias_def); + let generic_params = self.lower_generic_params( + HasImplicitSelf::Yes(trait_alias_def.type_bound_list()), + trait_alias_def, + ); + let ast_id = self.source_ast_id_map.ast_id(trait_alias_def); + + let alias = TraitAlias { name, visibility, generic_params, ast_id }; + Some(id(self.data().trait_aliases.alloc(alias))) } fn lower_impl(&mut self, impl_def: &ast::Impl) -> Option<FileItemTreeId<Impl>> { - let generic_params = self.lower_generic_params(GenericsOwner::Impl, impl_def); + // Note that trait impls don't get implicit `Self` unlike traits, because here they are a + // type alias rather than a type parameter, so this is handled by the resolver. + let generic_params = self.lower_generic_params(HasImplicitSelf::No, impl_def); // FIXME: If trait lowering fails, due to a non PathType for example, we treat this impl // as if it was an non-trait impl. Ideally we want to create a unique missing ref that only // equals itself. @@ -566,42 +595,29 @@ impl<'a> Ctx<'a> { fn lower_generic_params( &mut self, - owner: GenericsOwner<'_>, + has_implicit_self: HasImplicitSelf, node: &dyn ast::HasGenericParams, ) -> Interned<GenericParams> { let mut generics = GenericParams::default(); - match owner { - GenericsOwner::Function(_) - | GenericsOwner::Struct - | GenericsOwner::Enum - | GenericsOwner::Union - | GenericsOwner::TypeAlias => { - generics.fill(&self.body_ctx, node); - } - GenericsOwner::Trait(trait_def) => { - // traits get the Self type as an implicit first type parameter - generics.type_or_consts.alloc( - TypeParamData { - name: Some(name![Self]), - default: None, - provenance: TypeParamProvenance::TraitSelf, - } - .into(), - ); - // add super traits as bounds on Self - // i.e., trait Foo: Bar is equivalent to trait Foo where Self: Bar - let self_param = TypeRef::Path(name![Self].into()); - generics.fill_bounds(&self.body_ctx, trait_def, Either::Left(self_param)); - generics.fill(&self.body_ctx, node); - } - GenericsOwner::Impl => { - // Note that we don't add `Self` here: in `impl`s, `Self` is not a - // type-parameter, but rather is a type-alias for impl's target - // type, so this is handled by the resolver. - generics.fill(&self.body_ctx, node); - } + + if let HasImplicitSelf::Yes(bounds) = has_implicit_self { + // Traits and trait aliases get the Self type as an implicit first type parameter. + generics.type_or_consts.alloc( + TypeParamData { + name: Some(name![Self]), + default: None, + provenance: TypeParamProvenance::TraitSelf, + } + .into(), + ); + // add super traits as bounds on Self + // i.e., `trait Foo: Bar` is equivalent to `trait Foo where Self: Bar` + let self_param = TypeRef::Path(name![Self].into()); + generics.fill_bounds(&self.body_ctx, bounds, Either::Left(self_param)); } + generics.fill(&self.body_ctx, node); + generics.shrink_to_fit(); Interned::new(generics) } @@ -673,17 +689,10 @@ fn desugar_future_path(orig: TypeRef) -> Path { Path::from_known_path(path, generic_args) } -enum GenericsOwner<'a> { - /// We need access to the partially-lowered `Function` for lowering `impl Trait` in argument - /// position. - Function(&'a Function), - Struct, - Enum, - Union, - /// The `TraitDef` is needed to fill the source map for the implicit `Self` parameter. - Trait(&'a ast::Trait), - TypeAlias, - Impl, +enum HasImplicitSelf { + /// Inner list is a type bound list for the implicit `Self`. + Yes(Option<ast::TypeBoundList>), + No, } fn lower_abi(abi: ast::Abi) -> Interned<str> { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs index 8f230b87d..5f2999796 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/item_tree/pretty.rs @@ -374,23 +374,24 @@ impl<'a> Printer<'a> { } w!(self, "trait {}", name); self.print_generic_params(generic_params); - match items { - Some(items) => { - self.print_where_clause_and_opening_brace(generic_params); - self.indented(|this| { - for item in &**items { - this.print_mod_item((*item).into()); - } - }); - } - None => { - w!(self, " = "); - // FIXME: Print the aliased traits - self.print_where_clause_and_opening_brace(generic_params); + self.print_where_clause_and_opening_brace(generic_params); + self.indented(|this| { + for item in &**items { + this.print_mod_item((*item).into()); } - } + }); wln!(self, "}}"); } + ModItem::TraitAlias(it) => { + let TraitAlias { name, visibility, generic_params, ast_id: _ } = &self.tree[it]; + self.print_visibility(*visibility); + w!(self, "trait {}", name); + self.print_generic_params(generic_params); + w!(self, " = "); + self.print_where_clause(generic_params); + w!(self, ";"); + wln!(self); + } ModItem::Impl(it) => { let Impl { target_trait, self_ty, is_negative, items, generic_params, ast_id: _ } = &self.tree[it]; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs index 72beec818..f30be6b64 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/keys.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/keys.rs @@ -9,8 +9,8 @@ use syntax::{ast, AstNode, AstPtr}; use crate::{ dyn_map::{DynMap, Policy}, ConstId, EnumId, EnumVariantId, FieldId, FunctionId, ImplId, LifetimeParamId, Macro2Id, - MacroRulesId, ProcMacroId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, - UnionId, + MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, + TypeOrConstParamId, UnionId, }; pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>; @@ -21,6 +21,7 @@ pub const STATIC: Key<ast::Static, StaticId> = Key::new(); pub const TYPE_ALIAS: Key<ast::TypeAlias, TypeAliasId> = Key::new(); pub const IMPL: Key<ast::Impl, ImplId> = Key::new(); pub const TRAIT: Key<ast::Trait, TraitId> = Key::new(); +pub const TRAIT_ALIAS: Key<ast::TraitAlias, TraitAliasId> = Key::new(); pub const STRUCT: Key<ast::Struct, StructId> = Key::new(); pub const UNION: Key<ast::Union, UnionId> = Key::new(); pub const ENUM: Key<ast::Enum, EnumId> = Key::new(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs index ab9bc615d..4096e0a38 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lang_item.rs @@ -181,15 +181,15 @@ impl LangItems { T: Into<AttrDefId> + Copy, { let _p = profile::span("collect_lang_item"); - if let Some(lang_item) = lang_attr(db, item).and_then(|it| LangItem::from_str(&it)) { + if let Some(lang_item) = lang_attr(db, item) { self.items.entry(lang_item).or_insert_with(|| constructor(item)); } } } -pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<SmolStr> { +pub fn lang_attr(db: &dyn DefDatabase, item: impl Into<AttrDefId> + Copy) -> Option<LangItem> { let attrs = db.attrs(item.into()); - attrs.by_key("lang").string_value().cloned() + attrs.by_key("lang").string_value().cloned().and_then(|it| LangItem::from_str(&it)) } pub enum GenericRequirement { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index d07c5fb67..8c2e93f09 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -86,7 +86,7 @@ use crate::{ builtin_type::BuiltinType, item_tree::{ Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, MacroDef, MacroRules, ModItem, - Static, Struct, Trait, TypeAlias, Union, + Static, Struct, Trait, TraitAlias, TypeAlias, Union, }, }; @@ -128,7 +128,7 @@ impl ModuleId { } } -/// An ID of a module, **local** to a specific crate +/// An ID of a module, **local** to a `DefMap`. pub type LocalModuleId = Idx<nameres::ModuleData>; #[derive(Debug)] @@ -262,6 +262,11 @@ pub type TraitLoc = ItemLoc<Trait>; impl_intern!(TraitId, TraitLoc, intern_trait, lookup_intern_trait); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitAliasId(salsa::InternId); +pub type TraitAliasLoc = ItemLoc<TraitAlias>; +impl_intern!(TraitAliasId, TraitAliasLoc, intern_trait_alias, lookup_intern_trait_alias); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TypeAliasId(salsa::InternId); type TypeAliasLoc = AssocItemLoc<TypeAlias>; impl_intern!(TypeAliasId, TypeAliasLoc, intern_type_alias, lookup_intern_type_alias); @@ -453,6 +458,7 @@ pub enum ModuleDefId { ConstId(ConstId), StaticId(StaticId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), BuiltinType(BuiltinType), MacroId(MacroId), @@ -466,6 +472,7 @@ impl_from!( ConstId, StaticId, TraitId, + TraitAliasId, TypeAliasId, BuiltinType for ModuleDefId @@ -516,6 +523,7 @@ pub enum GenericDefId { FunctionId(FunctionId), AdtId(AdtId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), ImplId(ImplId), // enum variants cannot have generics themselves, but their parent enums @@ -528,6 +536,7 @@ impl_from!( FunctionId, AdtId(StructId, EnumId, UnionId), TraitId, + TraitAliasId, TypeAliasId, ImplId, EnumVariantId, @@ -555,6 +564,7 @@ pub enum AttrDefId { StaticId(StaticId), ConstId(ConstId), TraitId(TraitId), + TraitAliasId(TraitAliasId), TypeAliasId(TypeAliasId), MacroId(MacroId), ImplId(ImplId), @@ -714,6 +724,7 @@ impl HasModule for GenericDefId { GenericDefId::FunctionId(it) => it.lookup(db).module(db), GenericDefId::AdtId(it) => it.module(db), GenericDefId::TraitId(it) => it.lookup(db).container, + GenericDefId::TraitAliasId(it) => it.lookup(db).container, GenericDefId::TypeAliasId(it) => it.lookup(db).module(db), GenericDefId::ImplId(it) => it.lookup(db).container, GenericDefId::EnumVariantId(it) => it.parent.lookup(db).container, @@ -747,6 +758,7 @@ impl ModuleDefId { ModuleDefId::ConstId(id) => id.lookup(db).container.module(db), ModuleDefId::StaticId(id) => id.lookup(db).module(db), ModuleDefId::TraitId(id) => id.lookup(db).container, + ModuleDefId::TraitAliasId(id) => id.lookup(db).container, ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db), ModuleDefId::MacroId(id) => id.module(db), ModuleDefId::BuiltinType(_) => return None, @@ -765,6 +777,7 @@ impl AttrDefId { AttrDefId::StaticId(it) => it.lookup(db).module(db).krate, AttrDefId::ConstId(it) => it.lookup(db).module(db).krate, AttrDefId::TraitId(it) => it.lookup(db).container.krate, + AttrDefId::TraitAliasId(it) => it.lookup(db).container.krate, AttrDefId::TypeAliasId(it) => it.lookup(db).module(db).krate, AttrDefId::ImplId(it) => it.lookup(db).container.krate, AttrDefId::ExternBlockId(it) => it.lookup(db).container.krate, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs index 5ab90d92d..314bf22b9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests.rs @@ -20,7 +20,7 @@ use ::mbe::TokenMap; use base_db::{fixture::WithFixture, ProcMacro, SourceDatabase}; use expect_test::Expect; use hir_expand::{ - db::{AstDatabase, TokenExpander}, + db::{ExpandDatabase, TokenExpander}, AstId, InFile, MacroDefId, MacroDefKind, MacroFile, }; use stdx::format_to; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index bb4526672..0b72ca1ee 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -143,7 +143,7 @@ macro_rules! assert { fn main() { { - if !true { + if !(true ) { $crate::panic!("{} {:?}", arg1(a, b, c), arg2); } }; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs index 8358a46f0..b663a2917 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mbe/regression.rs @@ -827,6 +827,7 @@ macro_rules! rgb_color { /* parse error: expected type */ /* parse error: expected R_PAREN */ /* parse error: expected R_ANGLE */ +/* parse error: expected `::` */ /* parse error: expected COMMA */ /* parse error: expected R_ANGLE */ /* parse error: expected SEMICOLON */ diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index f42b0079d..4efe8c58a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -120,6 +120,8 @@ pub struct DefMap { registered_tools: Vec<SmolStr>, /// Unstable features of Rust enabled with `#![feature(A, B)]`. unstable_features: FxHashSet<SmolStr>, + /// #[rustc_coherence_is_core] + rustc_coherence_is_core: bool, edition: Edition, recursion_limit: Option<u32>, @@ -215,7 +217,7 @@ pub struct ModuleData { pub origin: ModuleOrigin, /// Declared visibility of this module. pub visibility: Visibility, - + /// Always [`None`] for block modules pub parent: Option<LocalModuleId>, pub children: FxHashMap<Name, LocalModuleId>, pub scope: ItemScope, @@ -292,6 +294,7 @@ impl DefMap { registered_tools: Vec::new(), unstable_features: FxHashSet::default(), diagnostics: Vec::new(), + rustc_coherence_is_core: false, } } @@ -325,6 +328,10 @@ impl DefMap { self.unstable_features.contains(feature) } + pub fn is_rustc_coherence_is_core(&self) -> bool { + self.rustc_coherence_is_core + } + pub fn root(&self) -> LocalModuleId { self.root } @@ -337,12 +344,12 @@ impl DefMap { self.proc_macro_loading_error.as_deref() } - pub(crate) fn krate(&self) -> CrateId { + pub fn krate(&self) -> CrateId { self.krate } pub(crate) fn block_id(&self) -> Option<BlockId> { - self.block.as_ref().map(|block| block.block) + self.block.map(|block| block.block) } pub(crate) fn prelude(&self) -> Option<ModuleId> { @@ -354,7 +361,7 @@ impl DefMap { } pub fn module_id(&self, local_id: LocalModuleId) -> ModuleId { - let block = self.block.as_ref().map(|b| b.block); + let block = self.block.map(|b| b.block); ModuleId { krate: self.krate, local_id, block } } @@ -425,12 +432,12 @@ impl DefMap { Some(self.block?.parent) } - /// Returns the module containing `local_mod`, either the parent `mod`, or the module containing + /// Returns the module containing `local_mod`, either the parent `mod`, or the module (or block) containing /// the block, if `self` corresponds to a block expression. pub fn containing_module(&self, local_mod: LocalModuleId) -> Option<ModuleId> { - match &self[local_mod].parent { - Some(parent) => Some(self.module_id(*parent)), - None => self.block.as_ref().map(|block| block.parent), + match self[local_mod].parent { + Some(parent) => Some(self.module_id(parent)), + None => self.block.map(|block| block.parent), } } @@ -440,11 +447,11 @@ impl DefMap { let mut buf = String::new(); let mut arc; let mut current_map = self; - while let Some(block) = ¤t_map.block { + while let Some(block) = current_map.block { go(&mut buf, current_map, "block scope", current_map.root); buf.push('\n'); arc = block.parent.def_map(db); - current_map = &*arc; + current_map = &arc; } go(&mut buf, current_map, "crate", current_map.root); return buf; @@ -468,10 +475,10 @@ impl DefMap { let mut buf = String::new(); let mut arc; let mut current_map = self; - while let Some(block) = ¤t_map.block { + while let Some(block) = current_map.block { format_to!(buf, "{:?} in {:?}\n", block.block, block.parent); arc = block.parent.def_map(db); - current_map = &*arc; + current_map = &arc; } format_to!(buf, "crate scope\n"); @@ -498,6 +505,7 @@ impl DefMap { krate: _, prelude: _, root: _, + rustc_coherence_is_core: _, } = self; extern_prelude.shrink_to_fit(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index 4b39a20d8..ddcee77ec 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -51,7 +51,8 @@ use crate::{ AdtId, AstId, AstIdWithPath, ConstLoc, EnumLoc, EnumVariantId, ExternBlockLoc, FunctionId, FunctionLoc, ImplLoc, Intern, ItemContainerId, LocalModuleId, Macro2Id, Macro2Loc, MacroExpander, MacroId, MacroRulesId, MacroRulesLoc, ModuleDefId, ModuleId, ProcMacroId, - ProcMacroLoc, StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, UnresolvedMacro, + ProcMacroLoc, StaticLoc, StructLoc, TraitAliasLoc, TraitLoc, TypeAliasLoc, UnionLoc, + UnresolvedMacro, }; static GLOB_RECURSION_LIMIT: Limit = Limit::new(100); @@ -86,10 +87,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: DefMap, tree_id: T // FIXME: a hacky way to create a Name from string. let name = tt::Ident { text: it.name.clone(), span: tt::TokenId::unspecified() }; - ( - name.as_name(), - ProcMacroExpander::new(def_map.krate, base_db::ProcMacroId(idx as u32)), - ) + (name.as_name(), ProcMacroExpander::new(base_db::ProcMacroId(idx as u32))) }) .collect() } @@ -298,6 +296,11 @@ impl DefCollector<'_> { continue; } + if attr_name.as_text().as_deref() == Some("rustc_coherence_is_core") { + self.def_map.rustc_coherence_is_core = true; + continue; + } + if *attr_name == hir_expand::name![feature] { let features = attr.parse_path_comma_token_tree().into_iter().flatten().filter_map( @@ -580,7 +583,7 @@ impl DefCollector<'_> { let kind = def.kind.to_basedb_kind(); let (expander, kind) = match self.proc_macros.iter().find(|(n, _)| n == &def.name) { Some(&(_, expander)) => (expander, kind), - None => (ProcMacroExpander::dummy(self.def_map.krate), kind), + None => (ProcMacroExpander::dummy(), kind), }; let proc_macro_id = @@ -666,8 +669,10 @@ impl DefCollector<'_> { macro_: Macro2Id, vis: &RawVisibility, ) { - let vis = - self.def_map.resolve_visibility(self.db, module_id, vis).unwrap_or(Visibility::Public); + let vis = self + .def_map + .resolve_visibility(self.db, module_id, vis, false) + .unwrap_or(Visibility::Public); self.def_map.modules[module_id].scope.declare(macro_.into()); self.update( module_id, @@ -831,7 +836,7 @@ impl DefCollector<'_> { let mut def = directive.status.namespaces(); let vis = self .def_map - .resolve_visibility(self.db, module_id, &directive.import.visibility) + .resolve_visibility(self.db, module_id, &directive.import.visibility, false) .unwrap_or(Visibility::Public); match import.kind { @@ -1547,7 +1552,7 @@ impl ModCollector<'_, '_> { }; let resolve_vis = |def_map: &DefMap, visibility| { def_map - .resolve_visibility(db, self.module_id, visibility) + .resolve_visibility(db, self.module_id, visibility, false) .unwrap_or(Visibility::Public) }; @@ -1707,6 +1712,20 @@ impl ModCollector<'_, '_> { false, ); } + ModItem::TraitAlias(id) => { + let it = &self.item_tree[id]; + + let vis = resolve_vis(def_map, &self.item_tree[it.visibility]); + update_def( + self.def_collector, + TraitAliasLoc { container: module, id: ItemTreeId::new(self.tree_id, id) } + .intern(db) + .into(), + &it.name, + vis, + false, + ); + } ModItem::TypeAlias(id) => { let it = &self.item_tree[id]; @@ -1823,7 +1842,7 @@ impl ModCollector<'_, '_> { ) -> LocalModuleId { let def_map = &mut self.def_collector.def_map; let vis = def_map - .resolve_visibility(self.def_collector.db, self.module_id, visibility) + .resolve_visibility(self.def_collector.db, self.module_id, visibility, false) .unwrap_or(Visibility::Public); let modules = &mut def_map.modules; let origin = match definition { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs index 1d9d5cccd..25478481d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/path_resolution.rs @@ -78,6 +78,7 @@ impl DefMap { // pub(path) // ^^^^ this visibility: &RawVisibility, + within_impl: bool, ) -> Option<Visibility> { let mut vis = match visibility { RawVisibility::Module(path) => { @@ -102,7 +103,8 @@ impl DefMap { // `super` to its parent (etc.). However, visibilities must only refer to a module in the // DefMap they're written in, so we restrict them when that happens. if let Visibility::Module(m) = vis { - if self.block_id() != m.block { + // ...unless we're resolving visibility for an associated item in an impl. + if self.block_id() != m.block && !within_impl { cov_mark::hit!(adjust_vis_in_block_def_map); vis = Visibility::Module(self.module_id(self.root())); tracing::debug!("visibility {:?} points outside DefMap, adjusting to {:?}", m, vis); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs index f5190b76d..13e6825f8 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/tests/incremental.rs @@ -223,6 +223,7 @@ pub type Ty = (); ModuleDefId::ConstId(it) => drop(db.const_data(it)), ModuleDefId::StaticId(it) => drop(db.static_data(it)), ModuleDefId::TraitId(it) => drop(db.trait_data(it)), + ModuleDefId::TraitAliasId(it) => drop(db.trait_alias_data(it)), ModuleDefId::TypeAliasId(it) => drop(db.type_alias_data(it)), ModuleDefId::EnumVariantId(_) | ModuleDefId::ModuleId(_) diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path.rs b/src/tools/rust-analyzer/crates/hir-def/src/path.rs index 36d4c36a2..f3197d180 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path.rs @@ -8,7 +8,7 @@ use std::{ use crate::{ body::LowerCtx, - type_ref::{ConstScalarOrPath, LifetimeRef}, + type_ref::{ConstRefOrPath, LifetimeRef}, }; use hir_expand::name::Name; use intern::Interned; @@ -85,7 +85,7 @@ pub struct AssociatedTypeBinding { pub enum GenericArg { Type(TypeRef), Lifetime(LifetimeRef), - Const(ConstScalarOrPath), + Const(ConstRefOrPath), } impl Path { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs index c85a11db6..b7542bd77 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/path/lower.rs @@ -2,7 +2,7 @@ use std::iter; -use crate::type_ref::ConstScalarOrPath; +use crate::type_ref::ConstRefOrPath; use either::Either; use hir_expand::name::{name, AsName}; @@ -212,7 +212,7 @@ pub(super) fn lower_generic_args( } } ast::GenericArg::ConstArg(arg) => { - let arg = ConstScalarOrPath::from_expr_opt(arg.expr()); + let arg = ConstRefOrPath::from_expr_opt(arg.expr()); args.push(GenericArg::Const(arg)) } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs index 86958e3da..61e64fc10 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/resolver.rs @@ -1,5 +1,5 @@ //! Name resolution façade. -use std::{hash::BuildHasherDefault, sync::Arc}; +use std::{fmt, hash::BuildHasherDefault, sync::Arc}; use base_db::CrateId; use hir_expand::name::{name, Name}; @@ -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, @@ -22,7 +22,8 @@ use crate::{ AdtId, AssocItemId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternBlockId, FunctionId, GenericDefId, GenericParamId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalModuleId, Lookup, Macro2Id, MacroId, MacroRulesId, ModuleDefId, ModuleId, ProcMacroId, - StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, VariantId, + StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, + VariantId, }; #[derive(Debug, Clone)] @@ -35,19 +36,34 @@ pub struct Resolver { module_scope: ModuleItemMap, } -#[derive(Debug, Clone)] +#[derive(Clone)] struct ModuleItemMap { def_map: Arc<DefMap>, module_id: LocalModuleId, } -#[derive(Debug, Clone)] +impl fmt::Debug for ModuleItemMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ModuleItemMap").field("module_id", &self.module_id).finish() + } +} + +#[derive(Clone)] struct ExprScope { owner: DefWithBodyId, expr_scopes: Arc<ExprScopes>, scope_id: ScopeId, } +impl fmt::Debug for ExprScope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ExprScope") + .field("owner", &self.owner) + .field("scope_id", &self.scope_id) + .finish() + } +} + #[derive(Debug, Clone)] enum Scope { /// All the items and imported names of a module @@ -74,6 +90,7 @@ pub enum TypeNs { TypeAliasId(TypeAliasId), BuiltinType(BuiltinType), TraitId(TraitId), + TraitAliasId(TraitAliasId), // Module belong to type ns, but the resolver is used when all module paths // are fully resolved. // ModuleId(ModuleId) @@ -85,10 +102,10 @@ pub enum ResolveValueResult { Partial(TypeNs, usize), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum ValueNs { ImplSelf(ImplId), - LocalBinding(PatId), + LocalBinding(BindingId), FunctionId(FunctionId), ConstId(ConstId), StaticId(StaticId), @@ -214,10 +231,12 @@ impl Resolver { db: &dyn DefDatabase, visibility: &RawVisibility, ) -> Option<Visibility> { + let within_impl = + self.scopes().find(|scope| matches!(scope, Scope::ImplDefScope(_))).is_some(); match visibility { RawVisibility::Module(_) => { let (item_map, module) = self.item_scope(); - item_map.resolve_visibility(db, module, visibility) + item_map.resolve_visibility(db, module, visibility, within_impl) } RawVisibility::Public => Some(Visibility::Public), } @@ -236,69 +255,81 @@ impl Resolver { return self.module_scope.resolve_path_in_value_ns(db, path); } - for scope in self.scopes() { - match scope { - Scope::ExprScope(_) if n_segments > 1 => continue, - Scope::ExprScope(scope) => { - let entry = scope - .expr_scopes - .entries(scope.scope_id) - .iter() - .find(|entry| entry.name() == first_name); - - if let Some(e) = entry { - return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(e.pat()))); + if n_segments <= 1 { + for scope in self.scopes() { + match scope { + Scope::ExprScope(scope) => { + let entry = scope + .expr_scopes + .entries(scope.scope_id) + .iter() + .find(|entry| entry.name() == first_name); + + if let Some(e) = entry { + return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding( + e.binding(), + ))); + } } - } - Scope::GenericParams { params, def } if n_segments > 1 => { - if let Some(id) = params.find_type_by_name(first_name, *def) { - let ty = TypeNs::GenericParam(id); - return Some(ResolveValueResult::Partial(ty, 1)); + Scope::GenericParams { params, def } => { + if let Some(id) = params.find_const_by_name(first_name, *def) { + let val = ValueNs::GenericParam(id); + return Some(ResolveValueResult::ValueNs(val)); + } } - } - Scope::GenericParams { .. } if n_segments != 1 => continue, - Scope::GenericParams { params, def } => { - if let Some(id) = params.find_const_by_name(first_name, *def) { - let val = ValueNs::GenericParam(id); - return Some(ResolveValueResult::ValueNs(val)); + &Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + return Some(ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_))); + } } - } - - &Scope::ImplDefScope(impl_) => { - if first_name == &name![Self] { - return Some(if n_segments > 1 { - ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1) - } else { - ResolveValueResult::ValueNs(ValueNs::ImplSelf(impl_)) - }); + // bare `Self` doesn't work in the value namespace in a struct/enum definition + Scope::AdtScope(_) => continue, + Scope::BlockScope(m) => { + if let Some(def) = m.resolve_path_in_value_ns(db, path) { + return Some(def); + } } } - // bare `Self` doesn't work in the value namespace in a struct/enum definition - Scope::AdtScope(_) if n_segments == 1 => continue, - Scope::AdtScope(adt) => { - if first_name == &name![Self] { - let ty = TypeNs::AdtSelfType(*adt); - return Some(ResolveValueResult::Partial(ty, 1)); + } + } else { + for scope in self.scopes() { + match scope { + Scope::ExprScope(_) => continue, + Scope::GenericParams { params, def } => { + if let Some(id) = params.find_type_by_name(first_name, *def) { + let ty = TypeNs::GenericParam(id); + return Some(ResolveValueResult::Partial(ty, 1)); + } } - } - - Scope::BlockScope(m) => { - if let Some(def) = m.resolve_path_in_value_ns(db, path) { - return Some(def); + &Scope::ImplDefScope(impl_) => { + if first_name == &name![Self] { + return Some(ResolveValueResult::Partial(TypeNs::SelfType(impl_), 1)); + } + } + Scope::AdtScope(adt) => { + if first_name == &name![Self] { + let ty = TypeNs::AdtSelfType(*adt); + return Some(ResolveValueResult::Partial(ty, 1)); + } + } + Scope::BlockScope(m) => { + if let Some(def) = m.resolve_path_in_value_ns(db, path) { + return Some(def); + } } } } } - if let res @ Some(_) = self.module_scope.resolve_path_in_value_ns(db, path) { - return res; + if let Some(res) = self.module_scope.resolve_path_in_value_ns(db, path) { + return Some(res); } // If a path of the shape `u16::from_le_bytes` failed to resolve at all, then we fall back // to resolving to the primitive type, to allow this to still work in the presence of // `use core::u16;`. - if path.kind == PathKind::Plain && path.segments().len() > 1 { - if let Some(builtin) = BuiltinType::by_name(&path.segments()[0]) { + if path.kind == PathKind::Plain && n_segments > 1 { + if let Some(builtin) = BuiltinType::by_name(first_name) { return Some(ResolveValueResult::Partial(TypeNs::BuiltinType(builtin), 1)); } } @@ -400,6 +431,8 @@ impl Resolver { } pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> { + // FIXME(trait_alias): Trait alias brings aliased traits in scope! Note that supertraits of + // aliased traits are NOT brought in scope (unless also aliased). let mut traits = FxHashSet::default(); for scope in self.scopes() { @@ -428,6 +461,15 @@ impl Resolver { traits } + pub fn traits_in_scope_from_block_scopes(&self) -> impl Iterator<Item = TraitId> + '_ { + self.scopes() + .filter_map(|scope| match scope { + Scope::BlockScope(m) => Some(m.def_map[m.module_id].scope.traits()), + _ => None, + }) + .flatten() + } + pub fn module(&self) -> ModuleId { let (def_map, local_id) = self.item_scope(); def_map.module_id(local_id) @@ -459,14 +501,85 @@ impl Resolver { }) } + pub fn generic_params(&self) -> Option<&Interned<GenericParams>> { + self.scopes().find_map(|scope| match scope { + Scope::GenericParams { params, .. } => Some(params), + _ => None, + }) + } + pub fn body_owner(&self) -> Option<DefWithBodyId> { self.scopes().find_map(|scope| match scope { Scope::ExprScope(it) => Some(it.owner), _ => None, }) } + /// `expr_id` is required to be an expression id that comes after the top level expression scope in the given resolver + #[must_use] + pub fn update_to_inner_scope( + &mut self, + db: &dyn DefDatabase, + owner: DefWithBodyId, + expr_id: ExprId, + ) -> UpdateGuard { + #[inline(always)] + fn append_expr_scope( + db: &dyn DefDatabase, + resolver: &mut Resolver, + owner: DefWithBodyId, + expr_scopes: &Arc<ExprScopes>, + scope_id: ScopeId, + ) { + resolver.scopes.push(Scope::ExprScope(ExprScope { + owner, + expr_scopes: expr_scopes.clone(), + scope_id, + })); + if let Some(block) = expr_scopes.block(scope_id) { + if let Some(def_map) = db.block_def_map(block) { + let root = def_map.root(); + resolver + .scopes + .push(Scope::BlockScope(ModuleItemMap { def_map, module_id: root })); + // FIXME: This adds as many module scopes as there are blocks, but resolving in each + // already traverses all parents, so this is O(n²). I think we could only store the + // innermost module scope instead? + } + } + } + + let start = self.scopes.len(); + let innermost_scope = self.scopes().next(); + match innermost_scope { + Some(&Scope::ExprScope(ExprScope { scope_id, ref expr_scopes, owner })) => { + let expr_scopes = expr_scopes.clone(); + let scope_chain = expr_scopes + .scope_chain(expr_scopes.scope_for(expr_id)) + .take_while(|&it| it != scope_id); + for scope_id in scope_chain { + append_expr_scope(db, self, owner, &expr_scopes, scope_id); + } + } + _ => { + let expr_scopes = db.expr_scopes(owner); + let scope_chain = expr_scopes.scope_chain(expr_scopes.scope_for(expr_id)); + + for scope_id in scope_chain { + append_expr_scope(db, self, owner, &expr_scopes, scope_id); + } + } + } + self.scopes[start..].reverse(); + UpdateGuard(start) + } + + pub fn reset_to_guard(&mut self, UpdateGuard(start): UpdateGuard) { + self.scopes.truncate(start); + } } +pub struct UpdateGuard(usize); + impl Resolver { fn scopes(&self) -> impl Iterator<Item = &Scope> { self.scopes.iter().rev() @@ -504,7 +617,7 @@ pub enum ScopeDef { ImplSelfType(ImplId), AdtSelfType(AdtId), GenericParam(GenericParamId), - Local(PatId), + Local(BindingId), Label(LabelId), } @@ -556,17 +669,18 @@ 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()); }); } } } } -// needs arbitrary_self_types to be a method... or maybe move to the def? pub fn resolver_for_expr(db: &dyn DefDatabase, owner: DefWithBodyId, expr_id: ExprId) -> Resolver { + let r = owner.resolver(db); let scopes = db.expr_scopes(owner); - resolver_for_scope(db, owner, scopes.scope_for(expr_id)) + let scope_id = scopes.scope_for(expr_id); + resolver_for_scope_(db, scopes, scope_id, r, owner) } pub fn resolver_for_scope( @@ -574,8 +688,18 @@ pub fn resolver_for_scope( owner: DefWithBodyId, scope_id: Option<ScopeId>, ) -> Resolver { - let mut r = owner.resolver(db); + let r = owner.resolver(db); let scopes = db.expr_scopes(owner); + resolver_for_scope_(db, scopes, scope_id, r, owner) +} + +fn resolver_for_scope_( + db: &dyn DefDatabase, + scopes: Arc<ExprScopes>, + scope_id: Option<ScopeId>, + mut r: Resolver, + owner: DefWithBodyId, +) -> Resolver { let scope_chain = scopes.scope_chain(scope_id).collect::<Vec<_>>(); r.scopes.reserve(scope_chain.len()); @@ -641,6 +765,7 @@ impl ModuleItemMap { let ty = match module_def.take_types()? { ModuleDefId::AdtId(it) => TypeNs::AdtId(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + ModuleDefId::TraitAliasId(it) => TypeNs::TraitAliasId(it), ModuleDefId::TypeAliasId(it) => TypeNs::TypeAliasId(it), ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), @@ -678,6 +803,7 @@ fn to_value_ns(per_ns: PerNs) -> Option<ValueNs> { ModuleDefId::AdtId(AdtId::EnumId(_) | AdtId::UnionId(_)) | ModuleDefId::TraitId(_) + | ModuleDefId::TraitAliasId(_) | ModuleDefId::TypeAliasId(_) | ModuleDefId::BuiltinType(_) | ModuleDefId::MacroId(_) @@ -695,6 +821,7 @@ fn to_type_ns(per_ns: PerNs) -> Option<TypeNs> { ModuleDefId::BuiltinType(it) => TypeNs::BuiltinType(it), ModuleDefId::TraitId(it) => TypeNs::TraitId(it), + ModuleDefId::TraitAliasId(it) => TypeNs::TraitAliasId(it), ModuleDefId::FunctionId(_) | ModuleDefId::ConstId(_) @@ -732,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. // @@ -743,7 +870,7 @@ impl ScopeNames { cov_mark::hit!(shadowing_shows_single_completion); return; } - set.push(ScopeDef::Local(pat)) + set.push(ScopeDef::Local(binding)) } } @@ -779,6 +906,12 @@ impl HasResolver for TraitId { } } +impl HasResolver for TraitAliasId { + fn resolver(self, db: &dyn DefDatabase) -> Resolver { + self.lookup(db).container.resolver(db).push_generic_params_scope(db, self.into()) + } +} + impl<T: Into<AdtId> + Copy> HasResolver for T { fn resolver(self, db: &dyn DefDatabase) -> Resolver { let def = self.into(); @@ -858,6 +991,7 @@ impl HasResolver for GenericDefId { GenericDefId::FunctionId(inner) => inner.resolver(db), GenericDefId::AdtId(adt) => adt.resolver(db), GenericDefId::TraitId(inner) => inner.resolver(db), + GenericDefId::TraitAliasId(inner) => inner.resolver(db), GenericDefId::TypeAliasId(inner) => inner.resolver(db), GenericDefId::ImplId(inner) => inner.resolver(db), GenericDefId::EnumVariantId(inner) => inner.parent.resolver(db), diff --git a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs index b7908bdda..ee143b19a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/test_db.rs @@ -9,7 +9,7 @@ use base_db::{ salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, SourceDatabase, Upcast, }; -use hir_expand::{db::AstDatabase, InFile}; +use hir_expand::{db::ExpandDatabase, InFile}; use stdx::hash::NoHashHashSet; use syntax::{algo, ast, AstNode}; @@ -23,7 +23,7 @@ use crate::{ #[salsa::database( base_db::SourceDatabaseExtStorage, base_db::SourceDatabaseStorage, - hir_expand::db::AstDatabaseStorage, + hir_expand::db::ExpandDatabaseStorage, crate::db::InternDatabaseStorage, crate::db::DefDatabaseStorage )] @@ -40,8 +40,8 @@ impl Default for TestDB { } } -impl Upcast<dyn AstDatabase> for TestDB { - fn upcast(&self) -> &(dyn AstDatabase + 'static) { +impl Upcast<dyn ExpandDatabase> for TestDB { + fn upcast(&self) -> &(dyn ExpandDatabase + 'static) { &*self } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs index 9652b01b9..8e30f429a 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/type_ref.rs @@ -116,7 +116,7 @@ pub enum TypeRef { Reference(Box<TypeRef>, Option<LifetimeRef>, Mutability), // FIXME: for full const generics, the latter element (length) here is going to have to be an // expression that is further lowered later in hir_ty. - Array(Box<TypeRef>, ConstScalarOrPath), + Array(Box<TypeRef>, ConstRefOrPath), Slice(Box<TypeRef>), /// A fn pointer. Last element of the vector is the return type. Fn(Vec<(Option<Name>, TypeRef)>, bool /*varargs*/, bool /*is_unsafe*/), @@ -188,7 +188,7 @@ impl TypeRef { // `hir_def::body::lower` to lower this into an `Expr` and then evaluate it at the // `hir_ty` level, which would allow knowing the type of: // let v: [u8; 2 + 2] = [0u8; 4]; - let len = ConstScalarOrPath::from_expr_opt(inner.expr()); + let len = ConstRefOrPath::from_expr_opt(inner.expr()); TypeRef::Array(Box::new(TypeRef::from_ast_opt(ctx, inner.ty())), len) } ast::Type::SliceType(inner) => { @@ -378,25 +378,25 @@ impl TypeBound { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ConstScalarOrPath { - Scalar(ConstScalar), +pub enum ConstRefOrPath { + Scalar(ConstRef), Path(Name), } -impl std::fmt::Display for ConstScalarOrPath { +impl std::fmt::Display for ConstRefOrPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ConstScalarOrPath::Scalar(s) => s.fmt(f), - ConstScalarOrPath::Path(n) => n.fmt(f), + ConstRefOrPath::Scalar(s) => s.fmt(f), + ConstRefOrPath::Path(n) => n.fmt(f), } } } -impl ConstScalarOrPath { +impl ConstRefOrPath { pub(crate) fn from_expr_opt(expr: Option<ast::Expr>) -> Self { match expr { Some(x) => Self::from_expr(x), - None => Self::Scalar(ConstScalar::Unknown), + None => Self::Scalar(ConstRef::Unknown), } } @@ -407,7 +407,7 @@ impl ConstScalarOrPath { ast::Expr::PathExpr(p) => { match p.path().and_then(|x| x.segment()).and_then(|x| x.name_ref()) { Some(x) => Self::Path(x.as_name()), - None => Self::Scalar(ConstScalar::Unknown), + None => Self::Scalar(ConstRef::Unknown), } } ast::Expr::PrefixExpr(prefix_expr) => match prefix_expr.op_kind() { @@ -415,8 +415,8 @@ impl ConstScalarOrPath { let unsigned = Self::from_expr_opt(prefix_expr.expr()); // Add sign match unsigned { - Self::Scalar(ConstScalar::UInt(num)) => { - Self::Scalar(ConstScalar::Int(-(num as i128))) + Self::Scalar(ConstRef::UInt(num)) => { + Self::Scalar(ConstRef::Int(-(num as i128))) } other => other, } @@ -425,22 +425,22 @@ impl ConstScalarOrPath { }, ast::Expr::Literal(literal) => Self::Scalar(match literal.kind() { ast::LiteralKind::IntNumber(num) => { - num.value().map(ConstScalar::UInt).unwrap_or(ConstScalar::Unknown) + num.value().map(ConstRef::UInt).unwrap_or(ConstRef::Unknown) } ast::LiteralKind::Char(c) => { - c.value().map(ConstScalar::Char).unwrap_or(ConstScalar::Unknown) + c.value().map(ConstRef::Char).unwrap_or(ConstRef::Unknown) } - ast::LiteralKind::Bool(f) => ConstScalar::Bool(f), - _ => ConstScalar::Unknown, + ast::LiteralKind::Bool(f) => ConstRef::Bool(f), + _ => ConstRef::Unknown, }), - _ => Self::Scalar(ConstScalar::Unknown), + _ => Self::Scalar(ConstRef::Unknown), } } } /// A concrete constant value -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ConstScalar { +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ConstRef { Int(i128), UInt(u128), Bool(bool), @@ -454,18 +454,18 @@ pub enum ConstScalar { Unknown, } -impl ConstScalar { +impl ConstRef { pub fn builtin_type(&self) -> BuiltinType { match self { - ConstScalar::UInt(_) | ConstScalar::Unknown => BuiltinType::Uint(BuiltinUint::U128), - ConstScalar::Int(_) => BuiltinType::Int(BuiltinInt::I128), - ConstScalar::Char(_) => BuiltinType::Char, - ConstScalar::Bool(_) => BuiltinType::Bool, + ConstRef::UInt(_) | ConstRef::Unknown => BuiltinType::Uint(BuiltinUint::U128), + ConstRef::Int(_) => BuiltinType::Int(BuiltinInt::I128), + ConstRef::Char(_) => BuiltinType::Char, + ConstRef::Bool(_) => BuiltinType::Bool, } } } -impl From<Literal> for ConstScalar { +impl From<Literal> for ConstRef { fn from(literal: Literal) -> Self { match literal { Literal::Char(c) => Self::Char(c), @@ -477,14 +477,14 @@ impl From<Literal> for ConstScalar { } } -impl std::fmt::Display for ConstScalar { +impl std::fmt::Display for ConstRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { - ConstScalar::Int(num) => num.fmt(f), - ConstScalar::UInt(num) => num.fmt(f), - ConstScalar::Bool(flag) => flag.fmt(f), - ConstScalar::Char(c) => write!(f, "'{c}'"), - ConstScalar::Unknown => f.write_char('_'), + ConstRef::Int(num) => num.fmt(f), + ConstRef::UInt(num) => num.fmt(f), + ConstRef::Bool(flag) => flag.fmt(f), + ConstRef::Char(c) => write!(f, "'{c}'"), + ConstRef::Unknown => f.write_char('_'), } } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs index 087268a9e..ab76ed43d 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/visibility.rs @@ -11,7 +11,7 @@ use crate::{ nameres::DefMap, path::{ModPath, PathKind}, resolver::HasResolver, - ConstId, FunctionId, HasModule, LocalFieldId, ModuleId, VariantId, + ConstId, FunctionId, HasModule, LocalFieldId, LocalModuleId, ModuleId, VariantId, }; /// Visibility of an item, not yet resolved. @@ -120,7 +120,7 @@ impl Visibility { self, db: &dyn DefDatabase, def_map: &DefMap, - mut from_module: crate::LocalModuleId, + mut from_module: LocalModuleId, ) -> bool { let mut to_module = match self { Visibility::Module(m) => m, @@ -131,20 +131,23 @@ impl Visibility { // visibility as the containing module (even though no items are directly nameable from // there, getting this right is important for method resolution). // In that case, we adjust the visibility of `to_module` to point to the containing module. + // Additional complication: `to_module` might be in `from_module`'s `DefMap`, which we're // currently computing, so we must not call the `def_map` query for it. - let arc; - let to_module_def_map = - if to_module.krate == def_map.krate() && to_module.block == def_map.block_id() { - cov_mark::hit!(is_visible_from_same_block_def_map); - def_map - } else { - arc = to_module.def_map(db); - &arc - }; - let is_block_root = matches!(to_module.block, Some(_) if to_module_def_map[to_module.local_id].parent.is_none()); - if is_block_root { - to_module = to_module_def_map.containing_module(to_module.local_id).unwrap(); + let mut arc; + loop { + let to_module_def_map = + if to_module.krate == def_map.krate() && to_module.block == def_map.block_id() { + cov_mark::hit!(is_visible_from_same_block_def_map); + def_map + } else { + arc = to_module.def_map(db); + &arc + }; + match to_module_def_map.parent() { + Some(parent) => to_module = parent, + None => break, + } } // from_module needs to be a descendant of to_module diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs index 5c04f8e8b..8d1e88725 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs @@ -10,7 +10,7 @@ use smallvec::{smallvec, SmallVec}; use syntax::{ast, match_ast, AstNode, SmolStr, SyntaxNode}; use crate::{ - db::AstDatabase, + db::ExpandDatabase, hygiene::Hygiene, mod_path::{ModPath, PathKind}, name::AsName, @@ -38,7 +38,7 @@ impl ops::Deref for RawAttrs { impl RawAttrs { pub const EMPTY: Self = Self { entries: None }; - pub fn new(db: &dyn AstDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { + pub fn new(db: &dyn ExpandDatabase, owner: &dyn ast::HasAttrs, hygiene: &Hygiene) -> Self { let entries = collect_attrs(owner) .filter_map(|(id, attr)| match attr { Either::Left(attr) => { @@ -55,7 +55,7 @@ impl RawAttrs { Self { entries: if entries.is_empty() { None } else { Some(entries) } } } - pub fn from_attrs_owner(db: &dyn AstDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { + pub fn from_attrs_owner(db: &dyn ExpandDatabase, owner: InFile<&dyn ast::HasAttrs>) -> Self { let hygiene = Hygiene::new(db, owner.file_id); Self::new(db, owner.value, &hygiene) } @@ -87,7 +87,7 @@ impl RawAttrs { /// Processes `cfg_attr`s, returning the resulting semantic `Attrs`. // FIXME: This should return a different type - pub fn filter(self, db: &dyn AstDatabase, krate: CrateId) -> RawAttrs { + pub fn filter(self, db: &dyn ExpandDatabase, krate: CrateId) -> RawAttrs { let has_cfg_attrs = self .iter() .any(|attr| attr.path.as_ident().map_or(false, |name| *name == crate::name![cfg_attr])); @@ -199,7 +199,7 @@ impl fmt::Display for AttrInput { impl Attr { fn from_src( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, ast: ast::Meta, hygiene: &Hygiene, id: AttrId, @@ -221,7 +221,7 @@ impl Attr { } fn from_tt( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, tt: &tt::Subtree, hygiene: &Hygiene, id: AttrId, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs index 906ca991d..277ecd939 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs @@ -1,6 +1,6 @@ //! Builtin attributes. -use crate::{db::AstDatabase, name, tt, ExpandResult, MacroCallId, MacroCallKind}; +use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallId, MacroCallKind}; macro_rules! register_builtin { ( $(($name:ident, $variant:ident) => $expand:ident),* ) => { @@ -12,7 +12,7 @@ macro_rules! register_builtin { impl BuiltinAttrExpander { pub fn expand( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -60,7 +60,7 @@ pub fn find_builtin_attr(ident: &name::Name) -> Option<BuiltinAttrExpander> { } fn dummy_attr_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -90,7 +90,7 @@ fn dummy_attr_expand( /// So this hacky approach is a lot more friendly for us, though it does require a bit of support in /// [`hir::Semantics`] to make this work. fn derive_attr_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs index 060a68054..5c1a75132 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs @@ -9,7 +9,7 @@ use syntax::{ match_ast, }; -use crate::{db::AstDatabase, name, quote, ExpandError, ExpandResult, MacroCallId}; +use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult, MacroCallId}; macro_rules! register_builtin { ( $($trait:ident => $expand:ident),* ) => { @@ -21,7 +21,7 @@ macro_rules! register_builtin { impl BuiltinDeriveExpander { pub fn expand( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -141,7 +141,7 @@ fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResu ExpandResult::ok(expanded) } -fn find_builtin_crate(db: &dyn AstDatabase, id: MacroCallId) -> tt::TokenTree { +fn find_builtin_crate(db: &dyn ExpandDatabase, id: MacroCallId) -> tt::TokenTree { // FIXME: make hygiene works for builtin derive macro // such that $crate can be used here. let cg = db.crate_graph(); @@ -158,7 +158,7 @@ fn find_builtin_crate(db: &dyn AstDatabase, id: MacroCallId) -> tt::TokenTree { } fn copy_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -167,7 +167,7 @@ fn copy_expand( } fn clone_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -176,7 +176,7 @@ fn clone_expand( } fn default_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -185,7 +185,7 @@ fn default_expand( } fn debug_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -194,7 +194,7 @@ fn debug_expand( } fn hash_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -202,13 +202,17 @@ fn hash_expand( expand_simple_derive(tt, quote! { #krate::hash::Hash }) } -fn eq_expand(db: &dyn AstDatabase, id: MacroCallId, tt: &tt::Subtree) -> ExpandResult<tt::Subtree> { +fn eq_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult<tt::Subtree> { let krate = find_builtin_crate(db, id); expand_simple_derive(tt, quote! { #krate::cmp::Eq }) } fn partial_eq_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -217,7 +221,7 @@ fn partial_eq_expand( } fn ord_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -226,7 +230,7 @@ fn ord_expand( } fn partial_ord_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs index 9f3fa73d4..44510f2b7 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs @@ -10,7 +10,7 @@ use syntax::{ }; use crate::{ - db::AstDatabase, name, quote, tt, ExpandError, ExpandResult, MacroCallId, MacroCallLoc, + db::ExpandDatabase, name, quote, tt, ExpandError, ExpandResult, MacroCallId, MacroCallLoc, }; macro_rules! register_builtin { @@ -28,7 +28,7 @@ macro_rules! register_builtin { impl BuiltinFnLikeExpander { pub fn expand( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -42,7 +42,7 @@ macro_rules! register_builtin { impl EagerExpander { pub fn expand( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -121,7 +121,7 @@ const DOLLAR_CRATE: tt::Ident = tt::Ident { text: SmolStr::new_inline("$crate"), span: tt::TokenId::unspecified() }; fn module_path_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -130,7 +130,7 @@ fn module_path_expand( } fn line_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -144,7 +144,7 @@ fn line_expand( } fn log_syntax_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -152,7 +152,7 @@ fn log_syntax_expand( } fn trace_macros_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -160,7 +160,7 @@ fn trace_macros_expand( } fn stringify_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -174,7 +174,7 @@ fn stringify_expand( } fn column_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -188,7 +188,7 @@ fn column_expand( } fn assert_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -206,7 +206,7 @@ fn assert_expand( let cond = cond.clone(); let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); quote! {{ - if !#cond { + if !(#cond) { #DOLLAR_CRATE::panic!(##panic_args); } }} @@ -218,7 +218,7 @@ fn assert_expand( } fn file_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -234,7 +234,7 @@ fn file_expand( } fn format_args_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -276,7 +276,7 @@ fn format_args_expand( } fn asm_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -304,7 +304,7 @@ fn asm_expand( } fn global_asm_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, _tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -313,7 +313,7 @@ fn global_asm_expand( } fn cfg_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -325,7 +325,7 @@ fn cfg_expand( } fn panic_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -343,7 +343,7 @@ fn panic_expand( } fn unreachable_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -379,7 +379,7 @@ fn unquote_byte_string(lit: &tt::Literal) -> Option<Vec<u8>> { } fn compile_error_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -395,7 +395,7 @@ fn compile_error_expand( } fn concat_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -441,7 +441,7 @@ fn concat_expand( } fn concat_bytes_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -507,7 +507,7 @@ fn concat_bytes_expand_subtree( } fn concat_idents_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -529,7 +529,7 @@ fn concat_idents_expand( } fn relative_file( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, call_id: MacroCallId, path_str: &str, allow_recursion: bool, @@ -558,7 +558,7 @@ fn parse_string(tt: &tt::Subtree) -> Result<String, ExpandError> { } fn include_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -583,7 +583,7 @@ fn include_expand( } fn include_bytes_expand( - _db: &dyn AstDatabase, + _db: &dyn ExpandDatabase, _arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -606,7 +606,7 @@ fn include_bytes_expand( } fn include_str_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -637,13 +637,13 @@ fn include_str_expand( ExpandResult::ok(ExpandedEager::new(quote!(#text))) } -fn get_env_inner(db: &dyn AstDatabase, arg_id: MacroCallId, key: &str) -> Option<String> { +fn get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &str) -> Option<String> { let krate = db.lookup_intern_macro_call(arg_id).krate; db.crate_graph()[krate].env.get(key) } fn env_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { @@ -679,7 +679,7 @@ fn env_expand( } fn option_env_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, arg_id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<ExpandedEager> { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 76016274f..45572499e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -44,7 +44,7 @@ pub enum TokenExpander { impl TokenExpander { fn expand( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, tt: &tt::Subtree, ) -> ExpandResult<tt::Subtree> { @@ -83,9 +83,8 @@ impl TokenExpander { } } -// FIXME: rename to ExpandDatabase -#[salsa::query_group(AstDatabaseStorage)] -pub trait AstDatabase: SourceDatabase { +#[salsa::query_group(ExpandDatabaseStorage)] +pub trait ExpandDatabase: SourceDatabase { fn ast_id_map(&self, file_id: HirFileId) -> Arc<AstIdMap>; /// Main public API -- parses a hir file, not caring whether it's a real @@ -138,7 +137,7 @@ pub trait AstDatabase: SourceDatabase { /// token. The `token_to_map` mapped down into the expansion, with the mapped /// token returned. pub fn expand_speculative( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, actual_macro_call: MacroCallId, speculative_args: &SyntaxNode, token_to_map: SyntaxToken, @@ -211,7 +210,7 @@ pub fn expand_speculative( let mut speculative_expansion = match loc.def.kind { MacroDefKind::ProcMacro(expander, ..) => { tt.delimiter = tt::Delimiter::unspecified(); - expander.expand(db, loc.krate, &tt, attr_arg.as_ref()) + expander.expand(db, loc.def.krate, loc.krate, &tt, attr_arg.as_ref()) } MacroDefKind::BuiltInAttr(BuiltinAttrExpander::Derive, _) => { pseudo_derive_attr_expansion(&tt, attr_arg.as_ref()?) @@ -236,12 +235,12 @@ pub fn expand_speculative( Some((node.syntax_node(), token)) } -fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<AstIdMap> { +fn ast_id_map(db: &dyn ExpandDatabase, file_id: HirFileId) -> Arc<AstIdMap> { let map = db.parse_or_expand(file_id).map(|it| AstIdMap::from_source(&it)).unwrap_or_default(); Arc::new(map) } -fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNode> { +fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> Option<SyntaxNode> { match file_id.repr() { HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), HirFileIdRepr::MacroFile(macro_file) => { @@ -253,13 +252,13 @@ fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option<SyntaxNod } fn parse_macro_expansion( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, macro_file: MacroFile, ) -> ExpandResult<Option<(Parse<SyntaxNode>, Arc<mbe::TokenMap>)>> { let _p = profile::span("parse_macro_expansion"); - let result = db.macro_expand(macro_file.macro_call_id); + let mbe::ValueResult { value, err } = db.macro_expand(macro_file.macro_call_id); - if let Some(err) = &result.err { + if let Some(err) = &err { // Note: // The final goal we would like to make all parse_macro success, // such that the following log will not call anyway. @@ -280,9 +279,9 @@ fn parse_macro_expansion( parents ); } - let tt = match result.value { + let tt = match value { Some(tt) => tt, - None => return ExpandResult { value: None, err: result.err }, + None => return ExpandResult { value: None, err }, }; let expand_to = macro_expand_to(db, macro_file.macro_call_id); @@ -292,11 +291,11 @@ fn parse_macro_expansion( let (parse, rev_token_map) = token_tree_to_syntax_node(&tt, expand_to); - ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: result.err } + ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err } } fn macro_arg( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, id: MacroCallId, ) -> Option<Arc<(tt::Subtree, mbe::TokenMap, fixup::SyntaxFixupUndoInfo)>> { let arg = db.macro_arg_text(id)?; @@ -357,7 +356,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy .unwrap_or_default() } -fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { +fn macro_arg_text(db: &dyn ExpandDatabase, id: MacroCallId) -> Option<GreenNode> { let loc = db.lookup_intern_macro_call(id); let arg = loc.kind.arg(db)?; if matches!(loc.kind, MacroCallKind::FnLike { .. }) { @@ -380,7 +379,10 @@ fn macro_arg_text(db: &dyn AstDatabase, id: MacroCallId) -> Option<GreenNode> { Some(arg.green().into()) } -fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Result<Arc<TokenExpander>, mbe::ParseError> { +fn macro_def( + db: &dyn ExpandDatabase, + id: MacroDefId, +) -> Result<Arc<TokenExpander>, mbe::ParseError> { match id.kind { MacroDefKind::Declarative(ast_id) => { let (mac, def_site_token_map) = match ast_id.to_node(db) { @@ -419,7 +421,10 @@ fn macro_def(db: &dyn AstDatabase, id: MacroDefId) -> Result<Arc<TokenExpander>, } } -fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<Option<Arc<tt::Subtree>>> { +fn macro_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, +) -> ExpandResult<Option<Arc<tt::Subtree>>> { let _p = profile::span("macro_expand"); let loc: MacroCallLoc = db.lookup_intern_macro_call(id); if let Some(eager) = &loc.eager { @@ -469,11 +474,11 @@ fn macro_expand(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<Option<Ar ExpandResult { value: Some(Arc::new(tt)), err } } -fn macro_expand_error(db: &dyn AstDatabase, macro_call: MacroCallId) -> Option<ExpandError> { +fn macro_expand_error(db: &dyn ExpandDatabase, macro_call: MacroCallId) -> Option<ExpandError> { db.macro_expand(macro_call).err } -fn expand_proc_macro(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<tt::Subtree> { +fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<tt::Subtree> { let loc: MacroCallLoc = db.lookup_intern_macro_call(id); let macro_arg = match db.macro_arg(id) { Some(it) => it, @@ -499,14 +504,14 @@ fn expand_proc_macro(db: &dyn AstDatabase, id: MacroCallId) -> ExpandResult<tt:: _ => None, }; - expander.expand(db, loc.krate, ¯o_arg.0, attr_arg.as_ref()) + expander.expand(db, loc.def.krate, loc.krate, ¯o_arg.0, attr_arg.as_ref()) } -fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> { +fn hygiene_frame(db: &dyn ExpandDatabase, file_id: HirFileId) -> Arc<HygieneFrame> { Arc::new(HygieneFrame::new(db, file_id)) } -fn macro_expand_to(db: &dyn AstDatabase, id: MacroCallId) -> ExpandTo { +fn macro_expand_to(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandTo { let loc: MacroCallLoc = db.lookup_intern_macro_call(id); loc.kind.expand_to() } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs index dfab7ec92..aca41b11f 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/eager.rs @@ -25,7 +25,7 @@ use syntax::{ted, SyntaxNode}; use crate::{ ast::{self, AstNode}, - db::AstDatabase, + db::ExpandDatabase, hygiene::Hygiene, mod_path::ModPath, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, InFile, MacroCallId, MacroCallKind, @@ -96,7 +96,7 @@ impl ErrorSink for &'_ mut dyn FnMut(ExpandError) { } pub fn expand_eager_macro( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, krate: CrateId, macro_call: InFile<ast::MacroCall>, def: MacroDefId, @@ -172,7 +172,7 @@ fn to_subtree(node: &SyntaxNode) -> crate::tt::Subtree { } fn lazy_expand( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, def: &MacroDefId, macro_call: InFile<ast::MacroCall>, krate: CrateId, @@ -193,7 +193,7 @@ fn lazy_expand( } fn eager_macro_recur( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, hygiene: &Hygiene, curr: InFile<SyntaxNode>, krate: CrateId, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs index c811d1c66..b273f2176 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/fixup.rs @@ -636,9 +636,8 @@ fn foo() { if {} } "#, - // the {} gets parsed as the condition, I think? expect![[r#" -fn foo () {if {} {}} +fn foo () {if __ra_fixup {} {}} "#]], ) } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index 2300ee9d0..2eb56fc9e 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -14,7 +14,7 @@ use syntax::{ }; use crate::{ - db::{self, AstDatabase}, + db::{self, ExpandDatabase}, fixup, name::{AsName, Name}, HirFileId, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile, @@ -26,7 +26,7 @@ pub struct Hygiene { } impl Hygiene { - pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { + pub fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Hygiene { Hygiene { frames: Some(HygieneFrames::new(db, file_id)) } } @@ -37,7 +37,7 @@ impl Hygiene { // FIXME: this should just return name pub fn name_ref_to_name( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, name_ref: ast::NameRef, ) -> Either<Name, CrateId> { if let Some(frames) = &self.frames { @@ -51,7 +51,7 @@ impl Hygiene { Either::Left(name_ref.as_name()) } - pub fn local_inner_macros(&self, db: &dyn AstDatabase, path: ast::Path) -> Option<CrateId> { + pub fn local_inner_macros(&self, db: &dyn ExpandDatabase, path: ast::Path) -> Option<CrateId> { let mut token = path.syntax().first_token()?.text_range(); let frames = self.frames.as_ref()?; let mut current = &frames.0; @@ -87,13 +87,13 @@ pub struct HygieneFrame { } impl HygieneFrames { - fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self { + fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Self { // Note that this intentionally avoids the `hygiene_frame` query to avoid blowing up memory // usage. The query is only helpful for nested `HygieneFrame`s as it avoids redundant work. HygieneFrames(Arc::new(HygieneFrame::new(db, file_id))) } - fn root_crate(&self, db: &dyn AstDatabase, node: &SyntaxNode) -> Option<CrateId> { + fn root_crate(&self, db: &dyn ExpandDatabase, node: &SyntaxNode) -> Option<CrateId> { let mut token = node.first_token()?.text_range(); let mut result = self.0.krate; let mut current = self.0.clone(); @@ -136,7 +136,7 @@ struct HygieneInfo { impl HygieneInfo { fn map_ident_up( &self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, token: TextRange, ) -> Option<(InFile<TextRange>, Origin)> { let token_id = self.exp_map.token_by_range(token)?; @@ -175,7 +175,7 @@ impl HygieneInfo { } fn make_hygiene_info( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, macro_file: MacroFile, loc: &MacroCallLoc, ) -> Option<HygieneInfo> { @@ -215,7 +215,7 @@ fn make_hygiene_info( } impl HygieneFrame { - pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame { + pub(crate) fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> HygieneFrame { let (info, krate, local_inner) = match file_id.macro_file() { None => (None, None, false), Some(macro_file) => { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index a52716cc0..5e99eacc1 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -198,7 +198,7 @@ impl HirFileId { /// For macro-expansion files, returns the file original source file the /// expansion originated from. - pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId { + pub fn original_file(self, db: &dyn db::ExpandDatabase) -> FileId { let mut file_id = self; loop { match file_id.repr() { @@ -214,7 +214,7 @@ impl HirFileId { } } - pub fn expansion_level(self, db: &dyn db::AstDatabase) -> u32 { + pub fn expansion_level(self, db: &dyn db::ExpandDatabase) -> u32 { let mut level = 0; let mut curr = self; while let Some(macro_file) = curr.macro_file() { @@ -227,14 +227,14 @@ impl HirFileId { } /// If this is a macro call, returns the syntax node of the call. - pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> { + pub fn call_node(self, db: &dyn db::ExpandDatabase) -> Option<InFile<SyntaxNode>> { let macro_file = self.macro_file()?; let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); Some(loc.kind.to_node(db)) } /// If this is a macro call, returns the syntax node of the very first macro call this file resides in. - pub fn original_call_node(self, db: &dyn db::AstDatabase) -> Option<(FileId, SyntaxNode)> { + pub fn original_call_node(self, db: &dyn db::ExpandDatabase) -> Option<(FileId, SyntaxNode)> { let mut call = db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).kind.to_node(db); loop { @@ -248,7 +248,7 @@ impl HirFileId { } /// Return expansion information if it is a macro-expansion file - pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> { + pub fn expansion_info(self, db: &dyn db::ExpandDatabase) -> Option<ExpansionInfo> { let macro_file = self.macro_file()?; let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); @@ -294,7 +294,7 @@ impl HirFileId { } /// Indicate it is macro file generated for builtin derive - pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Attr>> { + pub fn is_builtin_derive(&self, db: &dyn db::ExpandDatabase) -> Option<InFile<ast::Attr>> { let macro_file = self.macro_file()?; let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); let attr = match loc.def.kind { @@ -304,7 +304,7 @@ impl HirFileId { Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) } - pub fn is_custom_derive(&self, db: &dyn db::AstDatabase) -> bool { + pub fn is_custom_derive(&self, db: &dyn db::ExpandDatabase) -> bool { match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); @@ -315,7 +315,7 @@ impl HirFileId { } /// Return whether this file is an include macro - pub fn is_include_macro(&self, db: &dyn db::AstDatabase) -> bool { + pub fn is_include_macro(&self, db: &dyn db::ExpandDatabase) -> bool { match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); @@ -326,7 +326,7 @@ impl HirFileId { } /// Return whether this file is an attr macro - pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool { + pub fn is_attr_macro(&self, db: &dyn db::ExpandDatabase) -> bool { match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); @@ -338,7 +338,7 @@ impl HirFileId { /// Return whether this file is the pseudo expansion of the derive attribute. /// See [`crate::builtin_attr_macro::derive_attr_expand`]. - pub fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::AstDatabase) -> bool { + pub fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::ExpandDatabase) -> bool { match self.macro_file() { Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); @@ -384,7 +384,7 @@ impl HirFileId { impl MacroDefId { pub fn as_lazy_macro( self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, krate: CrateId, kind: MacroCallKind, ) -> MacroCallId { @@ -427,7 +427,7 @@ impl MacroCallKind { } } - pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> { + pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> InFile<SyntaxNode> { match self { MacroCallKind::FnLike { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) @@ -465,7 +465,7 @@ impl MacroCallKind { /// Returns the original file range that best describes the location of this macro call. /// /// Unlike `MacroCallKind::original_call_range`, this also spans the item of attributes and derives. - pub fn original_call_range_with_body(self, db: &dyn db::AstDatabase) -> FileRange { + pub fn original_call_range_with_body(self, db: &dyn db::ExpandDatabase) -> FileRange { let mut kind = self; let file_id = loop { match kind.file_id().repr() { @@ -490,7 +490,7 @@ impl MacroCallKind { /// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros /// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives /// get only the specific derive that is being referred to. - pub fn original_call_range(self, db: &dyn db::AstDatabase) -> FileRange { + pub fn original_call_range(self, db: &dyn db::ExpandDatabase) -> FileRange { let mut kind = self; let file_id = loop { match kind.file_id().repr() { @@ -529,7 +529,7 @@ impl MacroCallKind { FileRange { range, file_id } } - fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> { + fn arg(&self, db: &dyn db::ExpandDatabase) -> Option<SyntaxNode> { match self { MacroCallKind::FnLike { ast_id, .. } => { Some(ast_id.to_node(db).token_tree()?.syntax().clone()) @@ -597,7 +597,7 @@ impl ExpansionInfo { /// Both of these only have one simple call site input so no special handling is required here. pub fn map_token_down( &self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, item: Option<ast::Item>, token: InFile<&SyntaxToken>, ) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> { @@ -666,7 +666,7 @@ impl ExpansionInfo { /// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion. pub fn map_token_up( &self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, token: InFile<&SyntaxToken>, ) -> Option<(InFile<SyntaxToken>, Origin)> { // Fetch the id through its text range, @@ -717,7 +717,7 @@ impl ExpansionInfo { pub type AstId<N> = InFile<FileAstId<N>>; impl<N: AstNode> AstId<N> { - pub fn to_node(&self, db: &dyn db::AstDatabase) -> N { + pub fn to_node(&self, db: &dyn db::ExpandDatabase) -> N { let root = db.parse_or_expand(self.file_id).unwrap(); db.ast_id_map(self.file_id).get(self.value).to_node(&root) } @@ -753,7 +753,7 @@ impl<T> InFile<T> { self.with_value(&self.value) } - pub fn file_syntax(&self, db: &dyn db::AstDatabase) -> SyntaxNode { + pub fn file_syntax(&self, db: &dyn db::ExpandDatabase) -> SyntaxNode { db.parse_or_expand(self.file_id).expect("source created from invalid file") } } @@ -771,10 +771,19 @@ impl<T> InFile<Option<T>> { } } +impl<L, R> InFile<Either<L, R>> { + pub fn transpose(self) -> Either<InFile<L>, InFile<R>> { + match self.value { + Either::Left(l) => Either::Left(InFile::new(self.file_id, l)), + Either::Right(r) => Either::Right(InFile::new(self.file_id, r)), + } + } +} + impl<'a> InFile<&'a SyntaxNode> { pub fn ancestors_with_macros( self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, ) -> impl Iterator<Item = InFile<SyntaxNode>> + Clone + '_ { iter::successors(Some(self.cloned()), move |node| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), @@ -785,7 +794,7 @@ impl<'a> InFile<&'a SyntaxNode> { /// Skips the attributed item that caused the macro invocation we are climbing up pub fn ancestors_with_macros_skip_attr_item( self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ { let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() { Some(parent) => Some(node.with_value(parent)), @@ -806,8 +815,8 @@ impl<'a> InFile<&'a SyntaxNode> { /// Falls back to the macro call range if the node cannot be mapped up fully. /// /// For attributes and derives, this will point back to the attribute only. - /// For the entire item `InFile::use original_file_range_full`. - pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange { + /// For the entire item use [`InFile::original_file_range_full`]. + pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, HirFileIdRepr::MacroFile(mac_file) => { @@ -821,8 +830,23 @@ impl<'a> InFile<&'a SyntaxNode> { } } + /// Falls back to the macro call range if the node cannot be mapped up fully. + pub fn original_file_range_full(self, db: &dyn db::ExpandDatabase) -> FileRange { + match self.file_id.repr() { + HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, + HirFileIdRepr::MacroFile(mac_file) => { + if let Some(res) = self.original_file_range_opt(db) { + return res; + } + // Fall back to whole macro call. + let loc = db.lookup_intern_macro_call(mac_file.macro_call_id); + loc.kind.original_call_range_with_body(db) + } + } + } + /// Attempts to map the syntax node back up its macro calls. - pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option<FileRange> { + pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option<FileRange> { match ascend_node_border_tokens(db, self) { Some(InFile { file_id, value: (first, last) }) => { let original_file = file_id.original_file(db); @@ -841,7 +865,7 @@ impl<'a> InFile<&'a SyntaxNode> { } } - pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> { + pub fn original_syntax_node(self, db: &dyn db::ExpandDatabase) -> Option<InFile<SyntaxNode>> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input if !self.file_id.is_macro() { @@ -868,13 +892,13 @@ impl<'a> InFile<&'a SyntaxNode> { } impl InFile<SyntaxToken> { - pub fn upmap(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxToken>> { + pub fn upmap(self, db: &dyn db::ExpandDatabase) -> Option<InFile<SyntaxToken>> { let expansion = self.file_id.expansion_info(db)?; expansion.map_token_up(db, self.as_ref()).map(|(it, _)| it) } /// Falls back to the macro call range if the node cannot be mapped up fully. - pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange { + pub fn original_file_range(self, db: &dyn db::ExpandDatabase) -> FileRange { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, HirFileIdRepr::MacroFile(mac_file) => { @@ -889,7 +913,7 @@ impl InFile<SyntaxToken> { } /// Attempts to map the syntax node back up its macro calls. - pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option<FileRange> { + pub fn original_file_range_opt(self, db: &dyn db::ExpandDatabase) -> Option<FileRange> { match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { Some(FileRange { file_id, range: self.value.text_range() }) @@ -908,7 +932,7 @@ impl InFile<SyntaxToken> { pub fn ancestors_with_macros( self, - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ { self.value.parent().into_iter().flat_map({ let file_id = self.file_id; @@ -918,7 +942,7 @@ impl InFile<SyntaxToken> { } fn ascend_node_border_tokens( - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, InFile { file_id, value: node }: InFile<&SyntaxNode>, ) -> Option<InFile<(SyntaxToken, SyntaxToken)>> { let expansion = file_id.expansion_info(db)?; @@ -934,7 +958,7 @@ fn ascend_node_border_tokens( } fn ascend_call_token( - db: &dyn db::AstDatabase, + db: &dyn db::ExpandDatabase, expansion: &ExpansionInfo, token: InFile<SyntaxToken>, ) -> Option<InFile<SyntaxToken>> { @@ -953,7 +977,7 @@ impl<N: AstNode> InFile<N> { self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n)) } - pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> { + pub fn original_ast_node(self, db: &dyn db::ExpandDatabase) -> Option<InFile<N>> { // This kind of upmapping can only be achieved in attribute expanded files, // as we don't have node inputs otherwise and therefore can't find an `N` node in the input if !self.file_id.is_macro() { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs index d7586d129..e9393cc89 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/mod_path.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::{ - db::AstDatabase, + db::ExpandDatabase, hygiene::Hygiene, name::{known, Name}, }; @@ -37,7 +37,11 @@ pub enum PathKind { } impl ModPath { - pub fn from_src(db: &dyn AstDatabase, path: ast::Path, hygiene: &Hygiene) -> Option<ModPath> { + pub fn from_src( + db: &dyn ExpandDatabase, + path: ast::Path, + hygiene: &Hygiene, + ) -> Option<ModPath> { convert_path(db, None, path, hygiene) } @@ -162,7 +166,7 @@ impl From<Name> for ModPath { } fn convert_path( - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, prefix: Option<ModPath>, path: ast::Path, hygiene: &Hygiene, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs index 3f4d2540c..d758e9302 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs @@ -3,22 +3,20 @@ use base_db::{CrateId, ProcMacroExpansionError, ProcMacroId, ProcMacroKind}; use stdx::never; -use crate::{db::AstDatabase, tt, ExpandError, ExpandResult}; +use crate::{db::ExpandDatabase, tt, ExpandError, ExpandResult}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct ProcMacroExpander { - krate: CrateId, proc_macro_id: Option<ProcMacroId>, } impl ProcMacroExpander { - pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> Self { - Self { krate, proc_macro_id: Some(proc_macro_id) } + pub fn new(proc_macro_id: ProcMacroId) -> Self { + Self { proc_macro_id: Some(proc_macro_id) } } - pub fn dummy(krate: CrateId) -> Self { - // FIXME: Should store the name for better errors - Self { krate, proc_macro_id: None } + pub fn dummy() -> Self { + Self { proc_macro_id: None } } pub fn is_dummy(&self) -> bool { @@ -27,7 +25,8 @@ impl ProcMacroExpander { pub fn expand( self, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, + def_crate: CrateId, calling_crate: CrateId, tt: &tt::Subtree, attr_arg: Option<&tt::Subtree>, @@ -35,7 +34,7 @@ impl ProcMacroExpander { match self.proc_macro_id { Some(id) => { let krate_graph = db.crate_graph(); - let proc_macros = match &krate_graph[self.krate].proc_macro { + let proc_macros = match &krate_graph[def_crate].proc_macro { Ok(proc_macros) => proc_macros, Err(_) => { never!("Non-dummy expander even though there are no proc macros"); @@ -84,7 +83,7 @@ impl ProcMacroExpander { } None => ExpandResult::with_err( tt::Subtree::empty(), - ExpandError::UnresolvedProcMacro(self.krate), + ExpandError::UnresolvedProcMacro(def_crate), ), } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml index a8b8d5222..9b3296df2 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml +++ b/src/tools/rust-analyzer/crates/hir-ty/Cargo.toml @@ -18,13 +18,14 @@ arrayvec = "0.7.2" bitflags = "1.3.2" smallvec.workspace = true ena = "0.14.0" +either = "1.7.0" tracing = "0.1.35" rustc-hash = "1.1.0" scoped-tls = "1.0.0" -chalk-solve = { version = "0.88.0", default-features = false } -chalk-ir = "0.88.0" -chalk-recursive = { version = "0.88.0", default-features = false } -chalk-derive = "0.88.0" +chalk-solve = { version = "0.89.0", default-features = false } +chalk-ir = "0.89.0" +chalk-recursive = { version = "0.89.0", default-features = false } +chalk-derive = "0.89.0" la-arena = { version = "0.3.0", path = "../../lib/la-arena" } once_cell = "1.17.0" typed-arena = "2.0.1" diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs index 8faef7bf7..03e944359 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/builder.rs @@ -152,6 +152,15 @@ impl TyBuilder<()> { TyKind::Tuple(0, Substitution::empty(Interner)).intern(Interner) } + // FIXME: rustc's ty is dependent on the adt type, maybe we need to do that as well + pub fn discr_ty() -> Ty { + TyKind::Scalar(chalk_ir::Scalar::Int(chalk_ir::IntTy::I128)).intern(Interner) + } + + pub fn bool() -> Ty { + TyKind::Scalar(chalk_ir::Scalar::Bool).intern(Interner) + } + pub fn usize() -> Ty { TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)).intern(Interner) } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs index 6989e9fb9..28ae4c349 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_db.rs @@ -540,8 +540,7 @@ pub(crate) fn trait_datum_query( let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); let associated_ty_ids = trait_data.associated_types().map(to_assoc_type_id).collect(); let trait_datum_bound = rust_ir::TraitDatumBound { where_clauses }; - let well_known = lang_attr(db.upcast(), trait_) - .and_then(|name| well_known_trait_from_lang_item(LangItem::from_str(&name)?)); + let well_known = lang_attr(db.upcast(), trait_).and_then(well_known_trait_from_lang_item); let trait_datum = TraitDatum { id: trait_id, binders: make_binders(db, &generic_params, trait_datum_bound), diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs index 45c975dfc..214189492 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/chalk_ext.rs @@ -12,8 +12,8 @@ use hir_def::{ use crate::{ db::HirDatabase, from_assoc_type_id, from_chalk_trait_id, from_foreign_def_id, from_placeholder_idx, to_chalk_trait_id, utils::generics, AdtId, AliasEq, AliasTy, Binders, - CallableDefId, CallableSig, FnPointer, ImplTraitId, Interner, Lifetime, ProjectionTy, - QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, WhereClause, + CallableDefId, CallableSig, DynTy, FnPointer, ImplTraitId, Interner, Lifetime, ProjectionTy, + QuantifiedWhereClause, Substitution, TraitRef, Ty, TyBuilder, TyKind, TypeFlags, WhereClause, }; pub trait TyExt { @@ -22,6 +22,7 @@ pub trait TyExt { fn is_floating_point(&self) -> bool; fn is_never(&self) -> bool; fn is_unknown(&self) -> bool; + fn contains_unknown(&self) -> bool; fn is_ty_var(&self) -> bool; fn as_adt(&self) -> Option<(hir_def::AdtId, &Substitution)>; @@ -76,6 +77,10 @@ impl TyExt for Ty { matches!(self.kind(Interner), TyKind::Error) } + fn contains_unknown(&self) -> bool { + self.data(Interner).flags.contains(TypeFlags::HAS_ERROR) + } + fn is_ty_var(&self) -> bool { matches!(self.kind(Interner), TyKind::InferenceVar(_, _)) } @@ -373,6 +378,19 @@ impl ProjectionTyExt for ProjectionTy { } } +pub trait DynTyExt { + fn principal(&self) -> Option<&TraitRef>; +} + +impl DynTyExt for DynTy { + fn principal(&self) -> Option<&TraitRef> { + self.bounds.skip_binders().interned().get(0).and_then(|b| match b.skip_binders() { + crate::WhereClause::Implemented(trait_ref) => Some(trait_ref), + _ => None, + }) + } +} + pub trait TraitRefExt { fn hir_trait_id(&self) -> TraitId; } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs index 8df70330f..5830c4898 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval.rs @@ -1,30 +1,25 @@ //! Constant evaluation details -use std::{ - collections::HashMap, - fmt::{Display, Write}, -}; - -use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData, IntTy, Scalar}; +use base_db::CrateId; +use chalk_ir::{BoundVar, DebruijnIndex, GenericArgData}; use hir_def::{ - builtin_type::BuiltinInt, - expr::{ArithOp, BinaryOp, Expr, ExprId, Literal, Pat, PatId}, + expr::Expr, path::ModPath, - resolver::{resolver_for_expr, ResolveValueResult, Resolver, ValueNs}, - src::HasChildSource, - type_ref::ConstScalar, - ConstId, DefWithBodyId, EnumVariantId, Lookup, + resolver::{Resolver, ValueNs}, + type_ref::ConstRef, + ConstId, EnumVariantId, }; -use la_arena::{Arena, Idx, RawIdx}; +use la_arena::{Idx, RawIdx}; use stdx::never; -use syntax::ast::HasName; use crate::{ - db::HirDatabase, infer::InferenceContext, lower::ParamLoweringMode, to_placeholder_idx, - utils::Generics, Const, ConstData, ConstValue, GenericArg, InferenceResult, Interner, Ty, - TyBuilder, TyKind, + db::HirDatabase, infer::InferenceContext, layout::layout_of_ty, lower::ParamLoweringMode, + to_placeholder_idx, utils::Generics, Const, ConstData, ConstScalar, ConstValue, GenericArg, + Interner, MemoryMap, Ty, TyBuilder, }; +use super::mir::{interpret_mir, lower_to_mir, pad16, MirEvalError, MirLowerError}; + /// Extension trait for [`Const`] pub trait ConstExt { /// Is a [`Const`] unknown? @@ -53,346 +48,24 @@ impl ConstExt for Const { } } -pub struct ConstEvalCtx<'a> { - pub db: &'a dyn HirDatabase, - pub owner: DefWithBodyId, - pub exprs: &'a Arena<Expr>, - pub pats: &'a Arena<Pat>, - pub local_data: HashMap<PatId, ComputedExpr>, - infer: &'a InferenceResult, -} - -impl ConstEvalCtx<'_> { - fn expr_ty(&mut self, expr: ExprId) -> Ty { - self.infer[expr].clone() - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub enum ConstEvalError { - NotSupported(&'static str), - SemanticError(&'static str), - Loop, - IncompleteExpr, - Panic(String), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ComputedExpr { - Literal(Literal), - Enum(String, EnumVariantId, Literal), - Tuple(Box<[ComputedExpr]>), + MirLowerError(MirLowerError), + MirEvalError(MirEvalError), } -impl Display for ComputedExpr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ComputedExpr::Literal(l) => match l { - Literal::Int(x, _) => { - if *x >= 10 { - write!(f, "{x} ({x:#X})") - } else { - x.fmt(f) - } - } - Literal::Uint(x, _) => { - if *x >= 10 { - write!(f, "{x} ({x:#X})") - } else { - x.fmt(f) - } - } - Literal::Float(x, _) => x.fmt(f), - Literal::Bool(x) => x.fmt(f), - Literal::Char(x) => std::fmt::Debug::fmt(x, f), - Literal::String(x) => std::fmt::Debug::fmt(x, f), - Literal::ByteString(x) => std::fmt::Debug::fmt(x, f), - }, - ComputedExpr::Enum(name, _, _) => name.fmt(f), - ComputedExpr::Tuple(t) => { - f.write_char('(')?; - for x in &**t { - x.fmt(f)?; - f.write_str(", ")?; - } - f.write_char(')') - } +impl From<MirLowerError> for ConstEvalError { + fn from(value: MirLowerError) -> Self { + match value { + MirLowerError::ConstEvalError(e) => *e, + _ => ConstEvalError::MirLowerError(value), } } } -fn scalar_max(scalar: &Scalar) -> i128 { - match scalar { - Scalar::Bool => 1, - Scalar::Char => u32::MAX as i128, - Scalar::Int(x) => match x { - IntTy::Isize => isize::MAX as i128, - IntTy::I8 => i8::MAX as i128, - IntTy::I16 => i16::MAX as i128, - IntTy::I32 => i32::MAX as i128, - IntTy::I64 => i64::MAX as i128, - IntTy::I128 => i128::MAX, - }, - Scalar::Uint(x) => match x { - chalk_ir::UintTy::Usize => usize::MAX as i128, - chalk_ir::UintTy::U8 => u8::MAX as i128, - chalk_ir::UintTy::U16 => u16::MAX as i128, - chalk_ir::UintTy::U32 => u32::MAX as i128, - chalk_ir::UintTy::U64 => u64::MAX as i128, - chalk_ir::UintTy::U128 => i128::MAX, // ignore too big u128 for now - }, - Scalar::Float(_) => 0, - } -} - -fn is_valid(scalar: &Scalar, value: i128) -> bool { - if value < 0 { - !matches!(scalar, Scalar::Uint(_)) && -scalar_max(scalar) - 1 <= value - } else { - value <= scalar_max(scalar) - } -} - -fn get_name(ctx: &mut ConstEvalCtx<'_>, variant: EnumVariantId) -> String { - let loc = variant.parent.lookup(ctx.db.upcast()); - let children = variant.parent.child_source(ctx.db.upcast()); - let item_tree = loc.id.item_tree(ctx.db.upcast()); - - let variant_name = children.value[variant.local_id].name(); - let enum_name = item_tree[loc.id.value].name.to_string(); - enum_name + "::" + &variant_name.unwrap().to_string() -} - -pub fn eval_const( - expr_id: ExprId, - ctx: &mut ConstEvalCtx<'_>, -) -> Result<ComputedExpr, ConstEvalError> { - let u128_to_i128 = |it: u128| -> Result<i128, ConstEvalError> { - it.try_into().map_err(|_| ConstEvalError::NotSupported("u128 is too big")) - }; - - let expr = &ctx.exprs[expr_id]; - match expr { - Expr::Missing => match ctx.owner { - // evaluate the implicit variant index of an enum variant without expression - // FIXME: This should return the type of the enum representation - DefWithBodyId::VariantId(variant) => { - let prev_idx: u32 = variant.local_id.into_raw().into(); - let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw); - let value = match prev_idx { - Some(local_id) => { - let prev_variant = EnumVariantId { local_id, parent: variant.parent }; - 1 + match ctx.db.const_eval_variant(prev_variant)? { - ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, - _ => { - return Err(ConstEvalError::NotSupported( - "Enum can't contain this kind of value", - )) - } - } - } - _ => 0, - }; - Ok(ComputedExpr::Literal(Literal::Int(value, Some(BuiltinInt::I128)))) - } - _ => Err(ConstEvalError::IncompleteExpr), - }, - Expr::Literal(l) => Ok(ComputedExpr::Literal(l.clone())), - &Expr::UnaryOp { expr, op } => { - let ty = &ctx.expr_ty(expr); - let ev = eval_const(expr, ctx)?; - match op { - hir_def::expr::UnaryOp::Deref => Err(ConstEvalError::NotSupported("deref")), - hir_def::expr::UnaryOp::Not => { - let v = match ev { - ComputedExpr::Literal(Literal::Bool(b)) => { - return Ok(ComputedExpr::Literal(Literal::Bool(!b))) - } - ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, - _ => return Err(ConstEvalError::NotSupported("this kind of operator")), - }; - let r = match ty.kind(Interner) { - TyKind::Scalar(Scalar::Uint(x)) => match x { - chalk_ir::UintTy::U8 => !(v as u8) as i128, - chalk_ir::UintTy::U16 => !(v as u16) as i128, - chalk_ir::UintTy::U32 => !(v as u32) as i128, - chalk_ir::UintTy::U64 => !(v as u64) as i128, - chalk_ir::UintTy::U128 => { - return Err(ConstEvalError::NotSupported("negation of u128")) - } - chalk_ir::UintTy::Usize => !(v as usize) as i128, - }, - TyKind::Scalar(Scalar::Int(x)) => match x { - chalk_ir::IntTy::I8 => !(v as i8) as i128, - chalk_ir::IntTy::I16 => !(v as i16) as i128, - chalk_ir::IntTy::I32 => !(v as i32) as i128, - chalk_ir::IntTy::I64 => !(v as i64) as i128, - chalk_ir::IntTy::I128 => !v, - chalk_ir::IntTy::Isize => !(v as isize) as i128, - }, - _ => return Err(ConstEvalError::NotSupported("unreachable?")), - }; - Ok(ComputedExpr::Literal(Literal::Int(r, None))) - } - hir_def::expr::UnaryOp::Neg => { - let v = match ev { - ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, - _ => return Err(ConstEvalError::NotSupported("this kind of operator")), - }; - Ok(ComputedExpr::Literal(Literal::Int( - v.checked_neg().ok_or_else(|| { - ConstEvalError::Panic("overflow in negation".to_string()) - })?, - None, - ))) - } - } - } - &Expr::BinaryOp { lhs, rhs, op } => { - let ty = &ctx.expr_ty(lhs); - let lhs = eval_const(lhs, ctx)?; - let rhs = eval_const(rhs, ctx)?; - let op = op.ok_or(ConstEvalError::IncompleteExpr)?; - let v1 = match lhs { - ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, - _ => return Err(ConstEvalError::NotSupported("this kind of operator")), - }; - let v2 = match rhs { - ComputedExpr::Literal(Literal::Int(v, _)) => v, - ComputedExpr::Literal(Literal::Uint(v, _)) => u128_to_i128(v)?, - _ => return Err(ConstEvalError::NotSupported("this kind of operator")), - }; - match op { - BinaryOp::ArithOp(b) => { - let panic_arith = ConstEvalError::Panic( - "attempt to run invalid arithmetic operation".to_string(), - ); - let r = match b { - ArithOp::Add => v1.checked_add(v2).ok_or_else(|| panic_arith.clone())?, - ArithOp::Mul => v1.checked_mul(v2).ok_or_else(|| panic_arith.clone())?, - ArithOp::Sub => v1.checked_sub(v2).ok_or_else(|| panic_arith.clone())?, - ArithOp::Div => v1.checked_div(v2).ok_or_else(|| panic_arith.clone())?, - ArithOp::Rem => v1.checked_rem(v2).ok_or_else(|| panic_arith.clone())?, - ArithOp::Shl => v1 - .checked_shl(v2.try_into().map_err(|_| panic_arith.clone())?) - .ok_or_else(|| panic_arith.clone())?, - ArithOp::Shr => v1 - .checked_shr(v2.try_into().map_err(|_| panic_arith.clone())?) - .ok_or_else(|| panic_arith.clone())?, - ArithOp::BitXor => v1 ^ v2, - ArithOp::BitOr => v1 | v2, - ArithOp::BitAnd => v1 & v2, - }; - if let TyKind::Scalar(s) = ty.kind(Interner) { - if !is_valid(s, r) { - return Err(panic_arith); - } - } - Ok(ComputedExpr::Literal(Literal::Int(r, None))) - } - BinaryOp::LogicOp(_) => Err(ConstEvalError::SemanticError("logic op on numbers")), - _ => Err(ConstEvalError::NotSupported("bin op on this operators")), - } - } - Expr::Block { statements, tail, .. } => { - let mut prev_values = HashMap::<PatId, Option<ComputedExpr>>::default(); - for statement in &**statements { - match *statement { - hir_def::expr::Statement::Let { pat: pat_id, initializer, .. } => { - let pat = &ctx.pats[pat_id]; - match pat { - Pat::Bind { subpat, .. } if subpat.is_none() => (), - _ => { - return Err(ConstEvalError::NotSupported("complex patterns in let")) - } - }; - let value = match initializer { - Some(x) => eval_const(x, ctx)?, - None => continue, - }; - if !prev_values.contains_key(&pat_id) { - let prev = ctx.local_data.insert(pat_id, value); - prev_values.insert(pat_id, prev); - } else { - ctx.local_data.insert(pat_id, value); - } - } - hir_def::expr::Statement::Expr { .. } => { - return Err(ConstEvalError::NotSupported("this kind of statement")) - } - } - } - let r = match tail { - &Some(x) => eval_const(x, ctx), - None => Ok(ComputedExpr::Tuple(Box::new([]))), - }; - // clean up local data, so caller will receive the exact map that passed to us - for (name, val) in prev_values { - match val { - Some(x) => ctx.local_data.insert(name, x), - None => ctx.local_data.remove(&name), - }; - } - r - } - Expr::Path(p) => { - let resolver = resolver_for_expr(ctx.db.upcast(), ctx.owner, expr_id); - let pr = resolver - .resolve_path_in_value_ns(ctx.db.upcast(), p.mod_path()) - .ok_or(ConstEvalError::SemanticError("unresolved path"))?; - let pr = match pr { - ResolveValueResult::ValueNs(v) => v, - ResolveValueResult::Partial(..) => { - return match ctx - .infer - .assoc_resolutions_for_expr(expr_id) - .ok_or(ConstEvalError::SemanticError("unresolved assoc item"))? - .0 - { - hir_def::AssocItemId::FunctionId(_) => { - Err(ConstEvalError::NotSupported("assoc function")) - } - // FIXME use actual impl for trait assoc const - hir_def::AssocItemId::ConstId(c) => ctx.db.const_eval(c), - hir_def::AssocItemId::TypeAliasId(_) => { - Err(ConstEvalError::NotSupported("assoc type alias")) - } - }; - } - }; - match pr { - ValueNs::LocalBinding(pat_id) => { - let r = ctx - .local_data - .get(&pat_id) - .ok_or(ConstEvalError::NotSupported("Unexpected missing local"))?; - Ok(r.clone()) - } - ValueNs::ConstId(id) => ctx.db.const_eval(id), - ValueNs::GenericParam(_) => { - Err(ConstEvalError::NotSupported("const generic without substitution")) - } - ValueNs::EnumVariantId(id) => match ctx.db.const_eval_variant(id)? { - ComputedExpr::Literal(lit) => { - Ok(ComputedExpr::Enum(get_name(ctx, id), id, lit)) - } - _ => Err(ConstEvalError::NotSupported( - "Enums can't evalute to anything but numbers", - )), - }, - _ => Err(ConstEvalError::NotSupported("path that are not const or local")), - } - } - // FIXME: Handle the cast target - &Expr::Cast { expr, .. } => match eval_const(expr, ctx)? { - ComputedExpr::Enum(_, _, lit) => Ok(ComputedExpr::Literal(lit)), - _ => Err(ConstEvalError::NotSupported("Can't cast these types")), - }, - _ => Err(ConstEvalError::NotSupported("This kind of expression")), +impl From<MirEvalError> for ConstEvalError { + fn from(value: MirEvalError) -> Self { + ConstEvalError::MirEvalError(value) } } @@ -449,68 +122,102 @@ pub fn intern_const_scalar(value: ConstScalar, ty: Ty) -> Const { .intern(Interner) } +/// Interns a constant scalar with the given type +pub fn intern_const_ref(db: &dyn HirDatabase, value: &ConstRef, ty: Ty, krate: CrateId) -> Const { + let bytes = match value { + ConstRef::Int(i) => { + // FIXME: We should handle failure of layout better. + let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16); + ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default()) + } + ConstRef::UInt(i) => { + let size = layout_of_ty(db, &ty, krate).map(|x| x.size.bytes_usize()).unwrap_or(16); + ConstScalar::Bytes(i.to_le_bytes()[0..size].to_vec(), MemoryMap::default()) + } + ConstRef::Bool(b) => ConstScalar::Bytes(vec![*b as u8], MemoryMap::default()), + ConstRef::Char(c) => { + ConstScalar::Bytes((*c as u32).to_le_bytes().to_vec(), MemoryMap::default()) + } + ConstRef::Unknown => ConstScalar::Unknown, + }; + intern_const_scalar(bytes, ty) +} + /// Interns a possibly-unknown target usize -pub fn usize_const(value: Option<u128>) -> Const { - intern_const_scalar(value.map_or(ConstScalar::Unknown, ConstScalar::UInt), TyBuilder::usize()) +pub fn usize_const(db: &dyn HirDatabase, value: Option<u128>, krate: CrateId) -> Const { + intern_const_ref( + db, + &value.map_or(ConstRef::Unknown, ConstRef::UInt), + TyBuilder::usize(), + krate, + ) +} + +pub fn try_const_usize(c: &Const) -> Option<u128> { + match &c.data(Interner).value { + chalk_ir::ConstValue::BoundVar(_) => None, + chalk_ir::ConstValue::InferenceVar(_) => None, + chalk_ir::ConstValue::Placeholder(_) => None, + chalk_ir::ConstValue::Concrete(c) => match &c.interned { + ConstScalar::Bytes(x, _) => Some(u128::from_le_bytes(pad16(&x, false))), + _ => None, + }, + } } pub(crate) fn const_eval_recover( _: &dyn HirDatabase, _: &[String], _: &ConstId, -) -> Result<ComputedExpr, ConstEvalError> { - Err(ConstEvalError::Loop) +) -> Result<Const, ConstEvalError> { + Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } -pub(crate) fn const_eval_variant_recover( +pub(crate) fn const_eval_discriminant_recover( _: &dyn HirDatabase, _: &[String], _: &EnumVariantId, -) -> Result<ComputedExpr, ConstEvalError> { - Err(ConstEvalError::Loop) +) -> Result<i128, ConstEvalError> { + Err(ConstEvalError::MirLowerError(MirLowerError::Loop)) } -pub(crate) fn const_eval_variant_query( +pub(crate) fn const_eval_query( db: &dyn HirDatabase, const_id: ConstId, -) -> Result<ComputedExpr, ConstEvalError> { +) -> Result<Const, ConstEvalError> { let def = const_id.into(); - let body = db.body(def); - let infer = &db.infer(def); - let result = eval_const( - body.body_expr, - &mut ConstEvalCtx { - db, - owner: const_id.into(), - exprs: &body.exprs, - pats: &body.pats, - local_data: HashMap::default(), - infer, - }, - ); - result + let body = db.mir_body(def)?; + let c = interpret_mir(db, &body, false)?; + Ok(c) } -pub(crate) fn const_eval_query_variant( +pub(crate) fn const_eval_discriminant_variant( db: &dyn HirDatabase, variant_id: EnumVariantId, -) -> Result<ComputedExpr, ConstEvalError> { +) -> Result<i128, ConstEvalError> { let def = variant_id.into(); let body = db.body(def); - let infer = &db.infer(def); - eval_const( - body.body_expr, - &mut ConstEvalCtx { - db, - owner: def, - exprs: &body.exprs, - pats: &body.pats, - local_data: HashMap::default(), - infer, - }, - ) + if body.exprs[body.body_expr] == Expr::Missing { + let prev_idx: u32 = variant_id.local_id.into_raw().into(); + let prev_idx = prev_idx.checked_sub(1).map(RawIdx::from).map(Idx::from_raw); + let value = match prev_idx { + Some(local_id) => { + let prev_variant = EnumVariantId { local_id, parent: variant_id.parent }; + 1 + db.const_eval_discriminant(prev_variant)? + } + _ => 0, + }; + return Ok(value); + } + let mir_body = db.mir_body(def)?; + let c = interpret_mir(db, &mir_body, false)?; + let c = try_const_usize(&c).unwrap() as i128; + Ok(c) } +// FIXME: Ideally constants in const eval should have separate body (issue #7434), and this function should +// get an `InferenceResult` instead of an `InferenceContext`. And we should remove `ctx.clone().resolve_all()` here +// and make this function private. See the fixme comment on `InferenceContext::resolve_all`. pub(crate) fn eval_to_const( expr: Idx<Expr>, mode: ParamLoweringMode, @@ -518,28 +225,20 @@ pub(crate) fn eval_to_const( args: impl FnOnce() -> Generics, debruijn: DebruijnIndex, ) -> Const { + let db = ctx.db; if let Expr::Path(p) = &ctx.body.exprs[expr] { - let db = ctx.db; let resolver = &ctx.resolver; if let Some(c) = path_to_const(db, resolver, p.mod_path(), mode, args, debruijn) { return c; } } - let body = ctx.body.clone(); - let mut ctx = ConstEvalCtx { - db: ctx.db, - owner: ctx.owner, - exprs: &body.exprs, - pats: &body.pats, - local_data: HashMap::default(), - infer: &ctx.result, - }; - let computed_expr = eval_const(expr, &mut ctx); - let const_scalar = match computed_expr { - Ok(ComputedExpr::Literal(literal)) => literal.into(), - _ => ConstScalar::Unknown, - }; - intern_const_scalar(const_scalar, TyBuilder::usize()) + let infer = ctx.clone().resolve_all(); + if let Ok(mir_body) = lower_to_mir(ctx.db, ctx.owner, &ctx.body, &infer, expr) { + if let Ok(result) = interpret_mir(db, &mir_body, true) { + return result; + } + } + unknown_const(infer[expr].clone()) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs index 3c930c077..6a29e8ce5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/consteval/tests.rs @@ -1,24 +1,44 @@ use base_db::fixture::WithFixture; -use hir_def::{db::DefDatabase, expr::Literal}; +use hir_def::db::DefDatabase; -use crate::{consteval::ComputedExpr, db::HirDatabase, test_db::TestDB}; +use crate::{ + consteval::try_const_usize, db::HirDatabase, test_db::TestDB, Const, ConstScalar, Interner, +}; -use super::ConstEvalError; +use super::{ + super::mir::{MirEvalError, MirLowerError}, + ConstEvalError, +}; +fn simplify(e: ConstEvalError) -> ConstEvalError { + match e { + ConstEvalError::MirEvalError(MirEvalError::InFunction(_, e)) => { + simplify(ConstEvalError::MirEvalError(*e)) + } + _ => e, + } +} + +#[track_caller] fn check_fail(ra_fixture: &str, error: ConstEvalError) { - assert_eq!(eval_goal(ra_fixture), Err(error)); + assert_eq!(eval_goal(ra_fixture).map_err(simplify), Err(error)); } +#[track_caller] fn check_number(ra_fixture: &str, answer: i128) { let r = eval_goal(ra_fixture).unwrap(); - match r { - ComputedExpr::Literal(Literal::Int(r, _)) => assert_eq!(r, answer), - ComputedExpr::Literal(Literal::Uint(r, _)) => assert_eq!(r, answer as u128), - x => panic!("Expected number but found {x:?}"), + match &r.data(Interner).value { + chalk_ir::ConstValue::Concrete(c) => match &c.interned { + ConstScalar::Bytes(b, _) => { + assert_eq!(b, &answer.to_le_bytes()[0..b.len()]); + } + x => panic!("Expected number but found {:?}", x), + }, + _ => panic!("result of const eval wasn't a concrete const"), } } -fn eval_goal(ra_fixture: &str) -> Result<ComputedExpr, ConstEvalError> { +fn eval_goal(ra_fixture: &str) -> Result<Const, ConstEvalError> { let (db, file_id) = TestDB::with_single_file(ra_fixture); let module_id = db.module_for_file(file_id); let def_map = module_id.def_map(&db); @@ -42,21 +62,18 @@ fn eval_goal(ra_fixture: &str) -> Result<ComputedExpr, ConstEvalError> { #[test] fn add() { check_number(r#"const GOAL: usize = 2 + 2;"#, 4); + check_number(r#"const GOAL: i32 = -2 + --5;"#, 3); + check_number(r#"const GOAL: i32 = 7 - 5;"#, 2); + check_number(r#"const GOAL: i32 = 7 + (1 - 5);"#, 3); } #[test] fn bit_op() { check_number(r#"const GOAL: u8 = !0 & !(!0 >> 1)"#, 128); check_number(r#"const GOAL: i8 = !0 & !(!0 >> 1)"#, 0); - // FIXME: rustc evaluate this to -128 - check_fail( - r#"const GOAL: i8 = 1 << 7"#, - ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()), - ); - check_fail( - r#"const GOAL: i8 = 1 << 8"#, - ConstEvalError::Panic("attempt to run invalid arithmetic operation".to_string()), - ); + check_number(r#"const GOAL: i8 = 1 << 7"#, (1i8 << 7) as i128); + // FIXME: report panic here + check_number(r#"const GOAL: i8 = 1 << 8"#, 0); } #[test] @@ -74,6 +91,803 @@ fn locals() { } #[test] +fn references() { + check_number( + r#" + const GOAL: usize = { + let x = 3; + let y = &mut x; + *y = 5; + x + }; + "#, + 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] +fn reference_autoderef() { + check_number( + r#" + const GOAL: usize = { + let x = 3; + let y = &mut x; + let y: &mut usize = &mut y; + *y = 5; + x + }; + "#, + 5, + ); + check_number( + r#" + const GOAL: usize = { + let x = 3; + let y = &&&&&&&x; + let z: &usize = &y; + *z + }; + "#, + 3, + ); + check_number( + r#" + struct Foo<T> { x: T } + impl<T> Foo<T> { + fn foo(&mut self) -> T { self.x } + } + fn f(i: &mut &mut Foo<Foo<i32>>) -> i32 { + ((**i).x).foo() + } + fn g(i: Foo<Foo<i32>>) -> i32 { + i.x.foo() + } + const GOAL: i32 = f(&mut &mut Foo { x: Foo { x: 3 } }) + g(Foo { x: Foo { x: 5 } }); + "#, + 8, + ); +} + +#[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#" + const fn f(x: usize) -> usize { + 2 * x + 5 + } + const GOAL: usize = f(3); + "#, + 11, + ); + check_number( + r#" + const fn add(x: usize, y: usize) -> usize { + x + y + } + const GOAL: usize = add(add(1, 2), add(3, add(4, 5))); + "#, + 15, + ); +} + +#[test] +fn intrinsics() { + check_number( + r#" + extern "rust-intrinsic" { + pub fn size_of<T>() -> usize; + } + + const GOAL: usize = size_of::<i32>(); + "#, + 4, + ); +} + +#[test] +fn trait_basic() { + check_number( + r#" + trait Foo { + fn f(&self) -> u8; + } + + impl Foo for u8 { + fn f(&self) -> u8 { + *self + 33 + } + } + + const GOAL: u8 = { + let x = 3; + Foo::f(&x) + }; + "#, + 36, + ); +} + +#[test] +fn trait_method() { + check_number( + r#" + trait Foo { + fn f(&self) -> u8; + } + + impl Foo for u8 { + fn f(&self) -> u8 { + *self + 33 + } + } + + const GOAL: u8 = { + let x = 3; + x.f() + }; + "#, + 36, + ); +} + +#[test] +fn generic_fn() { + check_number( + r#" + trait Foo { + fn f(&self) -> u8; + } + + impl Foo for () { + fn f(&self) -> u8 { + 0 + } + } + + struct Succ<S>(S); + + impl<T: Foo> Foo for Succ<T> { + fn f(&self) -> u8 { + self.0.f() + 1 + } + } + + const GOAL: u8 = Succ(Succ(())).f(); + "#, + 2, + ); + check_number( + r#" + trait Foo { + fn f(&self) -> u8; + } + + impl Foo for u8 { + fn f(&self) -> u8 { + *self + 33 + } + } + + fn foof<T: Foo>(x: T, y: T) -> u8 { + x.f() + y.f() + } + + const GOAL: u8 = foof(2, 5); + "#, + 73, + ); + check_number( + r#" + fn bar<A, B>(a: A, b: B) -> B { + b + } + const GOAL: u8 = bar("hello", 12); + "#, + 12, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + fn bar<A, B>(a: A, b: B) -> B { + b + } + fn foo<T>(x: [T; 2]) -> T { + bar(x[0], x[1]) + } + + const GOAL: u8 = foo([2, 5]); + "#, + 5, + ); +} + +#[test] +fn impl_trait() { + check_number( + r#" + trait Foo { + fn f(&self) -> u8; + } + + impl Foo for u8 { + fn f(&self) -> u8 { + *self + 33 + } + } + + fn foof(x: impl Foo, y: impl Foo) -> impl Foo { + x.f() + y.f() + } + + const GOAL: u8 = foof(2, 5).f(); + "#, + 106, + ); + check_number( + r#" + struct Foo<T>(T, T, (T, T)); + trait S { + fn sum(&self) -> i64; + } + impl S for i64 { + fn sum(&self) -> i64 { + *self + } + } + impl<T: S> S for Foo<T> { + fn sum(&self) -> i64 { + self.0.sum() + self.1.sum() + self.2 .0.sum() + self.2 .1.sum() + } + } + + fn foo() -> Foo<impl S> { + Foo( + Foo(1i64, 2, (3, 4)), + Foo(5, 6, (7, 8)), + ( + Foo(9, 10, (11, 12)), + Foo(13, 14, (15, 16)), + ), + ) + } + const GOAL: i64 = foo().sum(); + "#, + 136, + ); +} + +#[test] +fn ifs() { + check_number( + r#" + const fn f(b: bool) -> u8 { + if b { 1 } else { 10 } + } + + const GOAL: u8 = f(true) + f(true) + f(false); + "#, + 12, + ); + check_number( + r#" + const fn max(a: i32, b: i32) -> i32 { + if a < b { b } else { a } + } + + const GOAL: i32 = max(max(1, max(10, 3)), 0-122); + "#, + 10, + ); + + check_number( + r#" + const fn max(a: &i32, b: &i32) -> &i32 { + if *a < *b { b } else { a } + } + + const GOAL: i32 = *max(max(&1, max(&10, &3)), &5); + "#, + 10, + ); +} + +#[test] +fn loops() { + check_number( + r#" + const GOAL: u8 = { + let mut x = 0; + loop { + x = x + 1; + while true { + break; + } + x = x + 1; + if x == 2 { + continue; + } + break; + } + x + }; + "#, + 4, + ); +} + +#[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#" + const fn fact(k: i32) -> i32 { + if k > 0 { fact(k - 1) * k } else { 1 } + } + + const GOAL: i32 = fact(5); + "#, + 120, + ); +} + +#[test] +fn structs() { + check_number( + r#" + struct Point { + x: i32, + y: i32, + } + + const GOAL: i32 = { + let p = Point { x: 5, y: 2 }; + let y = 1; + let x = 3; + let q = Point { y, x }; + p.x + p.y + p.x + q.y + q.y + q.x + }; + "#, + 17, + ); +} + +#[test] +fn unions() { + check_number( + r#" + union U { + f1: i64, + f2: (i32, i32), + } + + const GOAL: i32 = { + let p = U { f1: 0x0123ABCD0123DCBA }; + let p = unsafe { p.f2 }; + p.0 + p.1 + p.1 + }; + "#, + 0x0123ABCD * 2 + 0x0123DCBA, + ); +} + +#[test] +fn tuples() { + check_number( + r#" + const GOAL: u8 = { + let a = (10, 20, 3, 15); + a.1 + }; + "#, + 20, + ); + check_number( + r#" + const GOAL: u8 = { + let mut a = (10, 20, 3, 15); + a.1 = 2; + a.0 + a.1 + a.2 + a.3 + }; + "#, + 30, + ); + check_number( + r#" + struct TupleLike(i32, u8, i64, u16); + const GOAL: u8 = { + let a = TupleLike(10, 20, 3, 15); + a.1 + }; + "#, + 20, + ); + check_number( + r#" + const GOAL: u8 = { + match (&(2 + 2), &4) { + (left_val, right_val) => { + if !(*left_val == *right_val) { + 2 + } else { + 5 + } + } + } + }; + "#, + 5, + ); +} + +#[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#" + const fn f(x: &(u8, u8)) -> u8 { + match x { + (a, b) => *a + *b + } + } + const GOAL: u8 = f(&(2, 3)); + "#, + 5, + ); +} + +#[test] +fn let_else() { + check_number( + r#" + const fn f(x: &(u8, u8)) -> u8 { + let (a, b) = x; + *a + *b + } + const GOAL: u8 = f(&(2, 3)); + "#, + 5, + ); + check_number( + r#" + enum SingleVariant { + Var(u8, u8), + } + const fn f(x: &&&&&SingleVariant) -> u8 { + let SingleVariant::Var(a, b) = x; + *a + *b + } + const GOAL: u8 = f(&&&&&SingleVariant::Var(2, 3)); + "#, + 5, + ); + check_number( + r#" + //- minicore: option + const fn f(x: Option<i32>) -> i32 { + let Some(x) = x else { return 10 }; + 2 * x + } + 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#" + //- minicore: option + const GOAL: u8 = { + let x = Some(2); + match x { + Some(y) => 2 * y, + _ => 10, + } + }; + "#, + 4, + ); + check_number( + r#" + //- minicore: option + fn f(x: Option<Option<i32>>) -> i32 { + if let Some(y) = x && let Some(z) = y { + z + } else if let Some(y) = x { + 1 + } else { + 0 + } + } + const GOAL: i32 = f(Some(Some(10))) + f(Some(None)) + f(None); + "#, + 11, + ); + check_number( + r#" + //- minicore: option + const GOAL: u8 = { + let x = None; + match x { + Some(y) => 2 * y, + _ => 10, + } + }; + "#, + 10, + ); + check_number( + r#" + //- minicore: option + const GOAL: Option<&u8> = None; + "#, + 0, + ); +} + +#[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#" + //- minicore: coerce_unsized, index, slice + const GOAL: u8 = { + let a = [10, 20, 3, 15]; + let x: &[u8] = &a; + x[1] + }; + "#, + 20, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: usize = [1, 2, 3][2];"#, + 3, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: usize = { let a = [1, 2, 3]; let x: &[i32] = &a; x.len() };"#, + 3, + ); + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: usize = [1, 2, 3, 4, 5].len();"#, + 5, + ); +} + +#[test] +fn byte_string() { + check_number( + r#" + //- minicore: coerce_unsized, index, slice + const GOAL: u8 = { + let a = b"hello"; + let x: &[u8] = a; + x[0] + }; + "#, + 104, + ); +} + +#[test] fn consts() { check_number( r#" @@ -92,41 +906,35 @@ 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, ); let r = eval_goal( r#" - enum E { A = 1, } + enum E { A = 1, B } const GOAL: E = E::A; "#, ) .unwrap(); - match r { - ComputedExpr::Enum(name, _, Literal::Uint(val, _)) => { - assert_eq!(name, "E::A"); - assert_eq!(val, 1); - } - x => panic!("Expected enum but found {x:?}"), - } + assert_eq!(try_const_usize(&r), Some(1)); } #[test] @@ -138,7 +946,19 @@ fn const_loop() { const F2: i32 = 2 * F1; const GOAL: i32 = F3; "#, - ConstEvalError::Loop, + ConstEvalError::MirLowerError(MirLowerError::Loop), + ); +} + +#[test] +fn const_transfer_memory() { + check_number( + r#" + const A1: &i32 = &2; + const A2: &i32 = &5; + const GOAL: i32 = *A1 + *A2; + "#, + 7, ); } @@ -157,7 +977,20 @@ fn const_impl_assoc() { } #[test] -fn const_generic_subst() { +fn const_generic_subst_fn() { + check_number( + r#" + const fn f<const A: usize>(x: usize) -> usize { + A * x + 5 + } + const GOAL: usize = f::<2>(3); + "#, + 11, + ); +} + +#[test] +fn const_generic_subst_assoc_const_impl() { // FIXME: this should evaluate to 5 check_fail( r#" @@ -167,7 +1000,7 @@ fn const_generic_subst() { } const GOAL: usize = Adder::<2, 3>::VAL; "#, - ConstEvalError::NotSupported("const generic without substitution"), + ConstEvalError::MirEvalError(MirEvalError::TypeError("missing generic arg")), ); } @@ -185,6 +1018,58 @@ fn const_trait_assoc() { } const GOAL: usize = U0::VAL; "#, - ConstEvalError::IncompleteExpr, + ConstEvalError::MirLowerError(MirLowerError::IncompleteExpr), + ); +} + +#[test] +fn exec_limits() { + check_fail( + r#" + const GOAL: usize = loop {}; + "#, + ConstEvalError::MirEvalError(MirEvalError::ExecutionLimitExceeded), + ); + check_fail( + r#" + const fn f(x: i32) -> i32 { + f(x + 1) + } + const GOAL: i32 = f(0); + "#, + ConstEvalError::MirEvalError(MirEvalError::StackOverflow), + ); + // Reasonable code should still work + check_number( + r#" + const fn nth_odd(n: i32) -> i32 { + 2 * n - 1 + } + const fn f(n: i32) -> i32 { + let sum = 0; + let i = 0; + while i < n { + i = i + 1; + sum = sum + nth_odd(i); + } + sum + } + 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/src/tools/rust-analyzer/crates/hir-ty/src/db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs index d45e2a943..304c78767 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/db.rs @@ -16,10 +16,12 @@ use smallvec::SmallVec; use crate::{ chalk_db, - consteval::{ComputedExpr, ConstEvalError}, + consteval::ConstEvalError, method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, - Binders, CallableDefId, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, PolyFnSig, - QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, ValueTyDefId, + mir::{BorrowckResult, MirBody, MirLowerError}, + Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, + PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, + ValueTyDefId, }; use hir_expand::name::Name; @@ -32,6 +34,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[salsa::invoke(crate::infer::infer_query)] fn infer_query(&self, def: DefWithBodyId) -> Arc<InferenceResult>; + #[salsa::invoke(crate::mir::mir_body_query)] + #[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>; @@ -46,13 +55,13 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> { #[salsa::invoke(crate::lower::const_param_ty_query)] fn const_param_ty(&self, def: ConstParamId) -> Ty; - #[salsa::invoke(crate::consteval::const_eval_variant_query)] + #[salsa::invoke(crate::consteval::const_eval_query)] #[salsa::cycle(crate::consteval::const_eval_recover)] - fn const_eval(&self, def: ConstId) -> Result<ComputedExpr, ConstEvalError>; + fn const_eval(&self, def: ConstId) -> Result<Const, ConstEvalError>; - #[salsa::invoke(crate::consteval::const_eval_query_variant)] - #[salsa::cycle(crate::consteval::const_eval_variant_recover)] - fn const_eval_variant(&self, def: EnumVariantId) -> Result<ComputedExpr, ConstEvalError>; + #[salsa::invoke(crate::consteval::const_eval_discriminant_variant)] + #[salsa::cycle(crate::consteval::const_eval_discriminant_recover)] + fn const_eval_discriminant(&self, def: EnumVariantId) -> Result<i128, ConstEvalError>; #[salsa::invoke(crate::lower::impl_trait_query)] fn impl_trait(&self, def: ImplId) -> Option<Binders<TraitRef>>; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs index 37eb06be1..4b147b997 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics.rs @@ -11,3 +11,9 @@ pub use crate::diagnostics::{ }, unsafe_check::{missing_unsafe, unsafe_expressions, UnsafeExpr}, }; + +#[derive(Debug, PartialEq, Eq)] +pub struct IncoherentImpl { + pub file_id: hir_expand::HirFileId, + pub impl_: syntax::AstPtr<syntax::ast::Impl>, +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs index f7031a854..d36b93e3b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/decl_check.rs @@ -178,6 +178,7 @@ impl<'a> DeclValidator<'a> { AttrDefId::StaticId(sid) => Some(sid.lookup(self.db.upcast()).container.into()), AttrDefId::ConstId(cid) => Some(cid.lookup(self.db.upcast()).container.into()), AttrDefId::TraitId(tid) => Some(tid.lookup(self.db.upcast()).container.into()), + AttrDefId::TraitAliasId(taid) => Some(taid.lookup(self.db.upcast()).container.into()), AttrDefId::ImplId(iid) => Some(iid.lookup(self.db.upcast()).container.into()), AttrDefId::ExternBlockId(id) => Some(id.lookup(self.db.upcast()).container.into()), // These warnings should not explore macro definitions at all @@ -234,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/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs index 3286dcb5a..2e9066788 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/expr.rs @@ -5,11 +5,11 @@ use std::fmt; use std::sync::Arc; +use either::Either; use hir_def::lang_item::LangItem; use hir_def::{resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule}; use hir_def::{ItemContainerId, Lookup}; use hir_expand::name; -use itertools::Either; use itertools::Itertools; use rustc_hash::FxHashSet; use typed_arena::Arena; @@ -84,7 +84,7 @@ impl ExprValidator { match expr { Expr::Match { expr, arms } => { - self.validate_match(id, *expr, arms, db, self.infer.clone()); + self.validate_match(id, *expr, arms, db); } Expr::Call { .. } | Expr::MethodCall { .. } => { self.validate_call(db, id, expr, &mut filter_map_next_checker); @@ -147,16 +147,15 @@ impl ExprValidator { fn validate_match( &mut self, - id: ExprId, match_expr: ExprId, + scrutinee_expr: ExprId, arms: &[MatchArm], db: &dyn HirDatabase, - infer: Arc<InferenceResult>, ) { let body = db.body(self.owner); - let match_expr_ty = &infer[match_expr]; - if match_expr_ty.is_unknown() { + let scrut_ty = &self.infer[scrutinee_expr]; + if scrut_ty.is_unknown() { return; } @@ -166,23 +165,23 @@ impl ExprValidator { let mut m_arms = Vec::with_capacity(arms.len()); let mut has_lowering_errors = false; for arm in arms { - if let Some(pat_ty) = infer.type_of_pat.get(arm.pat) { + if let Some(pat_ty) = self.infer.type_of_pat.get(arm.pat) { // We only include patterns whose type matches the type - // of the match expression. If we had an InvalidMatchArmPattern + // of the scrutinee expression. If we had an InvalidMatchArmPattern // diagnostic or similar we could raise that in an else // block here. // // When comparing the types, we also have to consider that rustc - // will automatically de-reference the match expression type if + // will automatically de-reference the scrutinee expression type if // necessary. // // FIXME we should use the type checker for this. - if (pat_ty == match_expr_ty - || match_expr_ty + if (pat_ty == scrut_ty + || scrut_ty .as_reference() .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty) .unwrap_or(false)) - && types_of_subpatterns_do_match(arm.pat, &body, &infer) + && types_of_subpatterns_do_match(arm.pat, &body, &self.infer) { // If we had a NotUsefulMatchArm diagnostic, we could // check the usefulness of each pattern as we added it @@ -206,7 +205,7 @@ impl ExprValidator { return; } - let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty); + let report = compute_match_usefulness(&cx, &m_arms, scrut_ty); // FIXME Report unreacheble arms // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200 @@ -214,8 +213,8 @@ impl ExprValidator { let witnesses = report.non_exhaustiveness_witnesses; if !witnesses.is_empty() { self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { - match_expr: id, - uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms), + match_expr, + uncovered_patterns: missing_match_arms(&cx, scrut_ty, witnesses, arms), }); } } @@ -379,7 +378,7 @@ fn missing_match_arms<'p>( arms: &[MatchArm], ) -> String { struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>); - impl<'a, 'p> fmt::Display for DisplayWitness<'a, 'p> { + impl fmt::Display for DisplayWitness<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let DisplayWitness(witness, cx) = *self; let pat = witness.to_pat(cx); diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs index 8b0f051b4..859a37804 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/match_check.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs index 431ab949b..d25c0ccf0 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/diagnostics/unsafe_check.rs @@ -94,8 +94,10 @@ fn walk_unsafe( unsafe_expr_cb(UnsafeExpr { expr: current, inside_unsafe_block }); } } - Expr::Unsafe { body: child } => { - return walk_unsafe(db, infer, def, body, *child, true, unsafe_expr_cb); + Expr::Unsafe { .. } => { + return expr.walk_child_exprs(|child| { + walk_unsafe(db, infer, def, body, child, true, unsafe_expr_cb); + }); } _ => {} } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs index b22064d8c..bd3eccfe4 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/display.rs @@ -5,8 +5,9 @@ use std::fmt::{self, Debug}; use base_db::CrateId; -use chalk_ir::BoundVar; +use chalk_ir::{BoundVar, TyKind}; use hir_def::{ + adt::VariantData, body, db::DefDatabase, find_path, @@ -14,9 +15,9 @@ use hir_def::{ item_scope::ItemInNs, lang_item::{LangItem, LangItemTarget}, path::{Path, PathKind}, - type_ref::{ConstScalar, TraitBoundModifier, TypeBound, TypeRef}, + type_ref::{TraitBoundModifier, TypeBound, TypeRef}, visibility::Visibility, - HasModule, ItemContainerId, Lookup, ModuleDefId, ModuleId, TraitId, + HasModule, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, ModuleId, TraitId, }; use hir_expand::{hygiene::Hygiene, name::Name}; use intern::{Internable, Interned}; @@ -25,14 +26,17 @@ use smallvec::SmallVec; use crate::{ db::HirDatabase, - from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, lt_from_placeholder_idx, + from_assoc_type_id, from_foreign_def_id, from_placeholder_idx, + layout::layout_of_ty, + lt_from_placeholder_idx, mapping::from_chalk, + mir::pad16, primitive, to_assoc_type_id, utils::{self, generics}, - AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstValue, DomainGoal, - GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, Mutability, - OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, Substitution, TraitRef, - TraitRefExt, Ty, TyExt, TyKind, WhereClause, + AdtId, AliasEq, AliasTy, Binders, CallableDefId, CallableSig, Const, ConstScalar, ConstValue, + DomainGoal, GenericArg, ImplTraitId, Interner, Lifetime, LifetimeData, LifetimeOutlives, + MemoryMap, Mutability, OpaqueTy, ProjectionTy, ProjectionTyExt, QuantifiedWhereClause, Scalar, + Substitution, TraitRef, TraitRefExt, Ty, TyExt, WhereClause, }; pub trait HirWrite: fmt::Write { @@ -362,20 +366,176 @@ impl HirDisplay for GenericArg { impl HirDisplay for Const { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { let data = self.interned(); - match data.value { + match &data.value { ConstValue::BoundVar(idx) => idx.hir_fmt(f), ConstValue::InferenceVar(..) => write!(f, "#c#"), ConstValue::Placeholder(idx) => { - let id = from_placeholder_idx(f.db, idx); + let id = from_placeholder_idx(f.db, *idx); let generics = generics(f.db.upcast(), id.parent); let param_data = &generics.params.type_or_consts[id.local_id]; write!(f, "{}", param_data.name().unwrap()) } - ConstValue::Concrete(c) => write!(f, "{}", c.interned), + ConstValue::Concrete(c) => match &c.interned { + ConstScalar::Bytes(b, m) => render_const_scalar(f, &b, m, &data.ty), + ConstScalar::Unknown => f.write_char('_'), + }, } } } +pub struct HexifiedConst(pub Const); + +impl HirDisplay for HexifiedConst { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + let data = &self.0.data(Interner); + if let TyKind::Scalar(s) = data.ty.kind(Interner) { + if matches!(s, Scalar::Int(_) | Scalar::Uint(_)) { + if let ConstValue::Concrete(c) = &data.value { + if let ConstScalar::Bytes(b, m) = &c.interned { + let value = u128::from_le_bytes(pad16(b, false)); + if value >= 10 { + render_const_scalar(f, &b, m, &data.ty)?; + return write!(f, " ({:#X})", value); + } + } + } + } + } + self.0.hir_fmt(f) + } +} + +fn render_const_scalar( + f: &mut HirFormatter<'_>, + b: &[u8], + memory_map: &MemoryMap, + ty: &Ty, +) -> Result<(), HirDisplayError> { + match ty.kind(Interner) { + chalk_ir::TyKind::Scalar(s) => match s { + Scalar::Bool => write!(f, "{}", if b[0] == 0 { false } else { true }), + Scalar::Char => { + let x = u128::from_le_bytes(pad16(b, false)) as u32; + let Ok(c) = char::try_from(x) else { + return f.write_str("<unicode-error>"); + }; + write!(f, "{c:?}") + } + Scalar::Int(_) => { + let x = i128::from_le_bytes(pad16(b, true)); + write!(f, "{x}") + } + Scalar::Uint(_) => { + let x = u128::from_le_bytes(pad16(b, false)); + write!(f, "{x}") + } + Scalar::Float(fl) => match fl { + chalk_ir::FloatTy::F32 => { + let x = f32::from_le_bytes(b.try_into().unwrap()); + write!(f, "{x:?}") + } + chalk_ir::FloatTy::F64 => { + let x = f64::from_le_bytes(b.try_into().unwrap()); + write!(f, "{x:?}") + } + }, + }, + chalk_ir::TyKind::Ref(_, _, t) => match t.kind(Interner) { + chalk_ir::TyKind::Str => { + let addr = usize::from_le_bytes(b[0..b.len() / 2].try_into().unwrap()); + let bytes = memory_map.0.get(&addr).map(|x| &**x).unwrap_or(&[]); + let s = std::str::from_utf8(bytes).unwrap_or("<utf8-error>"); + write!(f, "{s:?}") + } + _ => f.write_str("<ref-not-supported>"), + }, + chalk_ir::TyKind::Tuple(_, subst) => { + // FIXME: Remove this line. If the target data layout is independent + // of the krate, the `db.target_data_layout` and its callers like `layout_of_ty` don't need + // to get krate. Otherwise, we need to get krate from the final callers of the hir display + // infrastructure and have it here as a field on `f`. + let krate = *f.db.crate_graph().crates_in_topological_order().last().unwrap(); + let Ok(layout) = layout_of_ty(f.db, ty, krate) else { + return f.write_str("<layout-error>"); + }; + f.write_str("(")?; + let mut first = true; + for (id, ty) in subst.iter(Interner).enumerate() { + if first { + first = false; + } else { + f.write_str(", ")?; + } + let ty = ty.assert_ty_ref(Interner); // Tuple only has type argument + let offset = layout.fields.offset(id).bytes_usize(); + let Ok(layout) = layout_of_ty(f.db, &ty, krate) else { + f.write_str("<layout-error>")?; + continue; + }; + let size = layout.size.bytes_usize(); + render_const_scalar(f, &b[offset..offset + size], memory_map, &ty)?; + } + f.write_str(")") + } + chalk_ir::TyKind::Adt(adt, subst) => match adt.0 { + hir_def::AdtId::StructId(s) => { + let data = f.db.struct_data(s); + let Ok(layout) = f.db.layout_of_adt(adt.0, subst.clone()) else { + return f.write_str("<layout-error>"); + }; + match data.variant_data.as_ref() { + VariantData::Record(fields) | VariantData::Tuple(fields) => { + let field_types = f.db.field_types(s.into()); + let krate = adt.0.module(f.db.upcast()).krate(); + let render_field = |f: &mut HirFormatter<'_>, id: LocalFieldId| { + let offset = layout + .fields + .offset(u32::from(id.into_raw()) as usize) + .bytes_usize(); + let ty = field_types[id].clone().substitute(Interner, subst); + let Ok(layout) = layout_of_ty(f.db, &ty, krate) else { + return f.write_str("<layout-error>"); + }; + let size = layout.size.bytes_usize(); + render_const_scalar(f, &b[offset..offset + size], memory_map, &ty) + }; + let mut it = fields.iter(); + if matches!(data.variant_data.as_ref(), VariantData::Record(_)) { + write!(f, "{} {{", data.name)?; + if let Some((id, data)) = it.next() { + write!(f, " {}: ", data.name)?; + render_field(f, id)?; + } + for (id, data) in it { + write!(f, ", {}: ", data.name)?; + render_field(f, id)?; + } + write!(f, " }}")?; + } else { + let mut it = it.map(|x| x.0); + write!(f, "{}(", data.name)?; + if let Some(id) = it.next() { + render_field(f, id)?; + } + for id in it { + write!(f, ", ")?; + render_field(f, id)?; + } + write!(f, ")")?; + } + return Ok(()); + } + VariantData::Unit => write!(f, "{}", data.name), + } + } + 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>"), + } +} + impl HirDisplay for BoundVar { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write!(f, "?{}.{}", self.debruijn.depth(), self.index) @@ -614,8 +774,9 @@ impl HirDisplay for Ty { { return true; } - if let Some(ConstValue::Concrete(c)) = - parameter.constant(Interner).map(|x| x.data(Interner).value) + if let Some(ConstValue::Concrete(c)) = parameter + .constant(Interner) + .map(|x| &x.data(Interner).value) { if c.interned == ConstScalar::Unknown { return true; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs index 767afdf9e..7de5b4295 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer.rs @@ -17,11 +17,12 @@ use std::ops::Index; use std::sync::Arc; use chalk_ir::{cast::Cast, ConstValue, DebruijnIndex, Mutability, Safety, Scalar, TypeFlags}; +use either::Either; 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, @@ -30,10 +31,9 @@ use hir_def::{ AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId, }; -use hir_expand::name::name; -use itertools::Either; +use hir_expand::name::{name, Name}; use la_arena::ArenaMap; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use stdx::always; use crate::{ @@ -66,8 +66,10 @@ pub(crate) fn infer_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Arc<Infer let mut ctx = InferenceContext::new(db, def, &body, resolver); match def { + DefWithBodyId::FunctionId(f) => { + ctx.collect_fn(f); + } DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), - DefWithBodyId::FunctionId(f) => ctx.collect_fn(f), DefWithBodyId::StaticId(s) => ctx.collect_static(&db.static_data(s)), DefWithBodyId::VariantId(v) => { ctx.return_ty = TyBuilder::builtin(match db.enum_data(v.parent).variant_body_type() { @@ -144,44 +146,6 @@ impl Default for BindingMode { } } -/// Used to generalize patterns and assignee expressions. -trait PatLike: Into<ExprOrPatId> + Copy { - type BindingMode: Copy; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty; -} - -impl PatLike for ExprId { - type BindingMode = (); - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - _: Self::BindingMode, - ) -> Ty { - this.infer_assignee_expr(id, expected_ty) - } -} - -impl PatLike for PatId { - type BindingMode = BindingMode; - - fn infer( - this: &mut InferenceContext<'_>, - id: Self, - expected_ty: &Ty, - default_bm: Self::BindingMode, - ) -> Ty { - this.infer_pat(id, expected_ty, default_bm) - } -} - #[derive(Debug)] pub(crate) struct InferOk<T> { value: T, @@ -200,11 +164,45 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>; #[derive(Debug, PartialEq, Eq, Clone)] pub enum InferenceDiagnostic { - NoSuchField { expr: ExprId }, - PrivateField { expr: ExprId, field: FieldId }, - PrivateAssocItem { id: ExprOrPatId, item: AssocItemId }, - BreakOutsideOfLoop { expr: ExprId, is_break: bool }, - MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize }, + NoSuchField { + expr: ExprId, + }, + PrivateField { + expr: ExprId, + field: FieldId, + }, + PrivateAssocItem { + id: ExprOrPatId, + item: AssocItemId, + }, + UnresolvedField { + expr: ExprId, + receiver: Ty, + name: Name, + method_with_same_name_exists: bool, + }, + UnresolvedMethodCall { + expr: ExprId, + receiver: Ty, + name: Name, + /// Contains the type the field resolves to + field_with_same_name: Option<Ty>, + }, + // FIXME: Make this proper + BreakOutsideOfLoop { + expr: ExprId, + is_break: bool, + bad_value_break: bool, + }, + MismatchedArgCount { + call_expr: ExprId, + expected: usize, + found: usize, + }, + ExpectedFunction { + call_expr: ExprId, + found: Ty, + }, } /// A mismatch between an expected and an inferred type. @@ -293,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 { @@ -354,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, @@ -389,18 +392,15 @@ impl InferenceResult { pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> { self.type_mismatches.get(&pat.into()) } + pub fn type_mismatches(&self) -> impl Iterator<Item = (ExprOrPatId, &TypeMismatch)> { + self.type_mismatches.iter().map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch)) + } pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatch)> { self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { ExprOrPatId::ExprId(expr) => Some((expr, mismatch)), _ => None, }) } - pub fn pat_type_mismatches(&self) -> impl Iterator<Item = (PatId, &TypeMismatch)> { - self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat { - ExprOrPatId::PatId(pat) => Some((pat, mismatch)), - _ => None, - }) - } } impl Index<ExprId> for InferenceResult { @@ -419,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> { @@ -428,14 +436,19 @@ pub(crate) struct InferenceContext<'a> { pub(crate) resolver: Resolver, table: unify::InferenceTable<'a>, trait_env: Arc<TraitEnvironment>, + /// The traits in scope, disregarding block modules. This is used for caching purposes. + traits_in_scope: FxHashSet<TraitId>, pub(crate) result: InferenceResult, /// The return type of the function being inferred, the closure or async block if we're /// currently within one. /// /// We might consider using a nested inference context for checking - /// closures, but currently this is the only field that will change there, - /// so it doesn't make sense. + /// closures so we can swap all shared things out at once. return_ty: Ty, + /// If `Some`, this stores coercion information for returned + /// expressions. If `None`, this is in a context where return is + /// inappropriate, such as a const expression. + return_coercion: Option<CoerceMany>, /// The resume type and the yield type, respectively, of the generator being inferred. resume_yield_tys: Option<(Ty, Ty)>, diverges: Diverges, @@ -447,7 +460,7 @@ struct BreakableContext { /// Whether this context contains at least one break expression. may_break: bool, /// The coercion target of the context. - coerce: CoerceMany, + coerce: Option<CoerceMany>, /// The optional label of the context. label: Option<name::Name>, kind: BreakableKind, @@ -503,16 +516,22 @@ impl<'a> InferenceContext<'a> { trait_env, return_ty: TyKind::Error.intern(Interner), // set in collect_* calls resume_yield_tys: None, + return_coercion: None, db, owner, body, + traits_in_scope: resolver.traits_in_scope(db.upcast()), resolver, diverges: Diverges::Maybe, breakables: Vec::new(), } } - fn resolve_all(self) -> InferenceResult { + // FIXME: This function should be private in module. It is currently only used in the consteval, since we need + // `InferenceResult` in the middle of inference. See the fixme comment in `consteval::eval_to_const`. If you + // used this function for another workaround, mention it here. If you really need this function and believe that + // there is no problem in it being `pub(crate)`, remove this comment. + pub(crate) fn resolve_all(self) -> InferenceResult { let InferenceContext { mut table, mut result, .. } = self; table.fallback_if_possible(); @@ -528,13 +547,46 @@ 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() { mismatch.expected = table.resolve_completely(mismatch.expected.clone()); mismatch.actual = table.resolve_completely(mismatch.actual.clone()); } + result.diagnostics.retain_mut(|diagnostic| { + if let InferenceDiagnostic::ExpectedFunction { found: ty, .. } + | InferenceDiagnostic::UnresolvedField { receiver: ty, .. } + | InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic + { + *ty = table.resolve_completely(ty.clone()); + // FIXME: Remove this when we are on par with rustc in terms of inference + if ty.contains_unknown() { + return false; + } + + if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } = + diagnostic + { + let clear = if let Some(ty) = field_with_same_name { + *ty = table.resolve_completely(ty.clone()); + ty.contains_unknown() + } else { + false + }; + if clear { + *field_with_same_name = None; + } + } + } + true + }); for (_, subst) in result.method_resolutions.values_mut() { *subst = table.resolve_completely(subst.clone()); } @@ -580,7 +632,7 @@ impl<'a> InferenceContext<'a> { let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); - self.infer_pat(*pat, &ty, BindingMode::default()); + self.infer_top_pat(*pat, &ty); } let error_ty = &TypeRef::Error; let return_ty = if data.has_async_kw() { @@ -632,10 +684,19 @@ impl<'a> InferenceContext<'a> { }; self.return_ty = self.normalize_associated_types_in(return_ty); + self.return_coercion = Some(CoerceMany::new(self.return_ty.clone())); } fn infer_body(&mut self) { - self.infer_expr_coerce(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); + match self.return_coercion { + Some(_) => self.infer_return(self.body.body_expr), + None => { + _ = self.infer_expr_coerce( + self.body.body_expr, + &Expectation::has_type(self.return_ty.clone()), + ) + } + } } fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) { @@ -662,12 +723,15 @@ 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); } fn make_ty(&mut self, type_ref: &TypeRef) -> Ty { - // FIXME use right resolver for block let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let ty = ctx.lower_ty(type_ref); let ty = self.insert_type_vars(ty); @@ -681,11 +745,9 @@ impl<'a> InferenceContext<'a> { /// Replaces ConstScalar::Unknown by a new type var, so we can maybe still infer it. fn insert_const_vars_shallow(&mut self, c: Const) -> Const { let data = c.data(Interner); - match data.value { + match &data.value { ConstValue::Concrete(cc) => match cc.interned { - hir_def::type_ref::ConstScalar::Unknown => { - self.table.new_const_var(data.ty.clone()) - } + crate::ConstScalar::Unknown => self.table.new_const_var(data.ty.clone()), _ => c, }, _ => c, @@ -785,12 +847,11 @@ impl<'a> InferenceContext<'a> { Some(path) => path, None => return (self.err_ty(), None), }; - let resolver = &self.resolver; let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); // FIXME: this should resolve assoc items as well, see this example: // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 let (resolution, unresolved) = if value_ns { - match resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path()) { Some(ResolveValueResult::ValueNs(value)) => match value { ValueNs::EnumVariantId(var) => { let substs = ctx.substs_from_path(path, var.into(), true); @@ -811,7 +872,7 @@ impl<'a> InferenceContext<'a> { None => return (self.err_ty(), None), } } else { - match resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { + match self.resolver.resolve_path_in_type_ns(self.db.upcast(), path.mod_path()) { Some(it) => it, None => return (self.err_ty(), None), } @@ -866,7 +927,10 @@ impl<'a> InferenceContext<'a> { // FIXME potentially resolve assoc type (self.err_ty(), None) } - TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => { + TypeNs::AdtId(AdtId::EnumId(_)) + | TypeNs::BuiltinType(_) + | TypeNs::TraitId(_) + | TypeNs::TraitAliasId(_) => { // FIXME diagnostic (self.err_ty(), None) } @@ -1018,6 +1082,15 @@ impl<'a> InferenceContext<'a> { let struct_ = self.resolve_lang_item(LangItem::VaList)?.as_struct()?; Some(struct_.into()) } + + fn get_traits_in_scope(&self) -> Either<FxHashSet<TraitId>, &FxHashSet<TraitId>> { + let mut b_traits = self.resolver.traits_in_scope_from_block_scopes().peekable(); + if b_traits.peek().is_some() { + Either::Left(self.traits_in_scope.iter().copied().chain(b_traits).collect()) + } else { + Either::Right(&self.traits_in_scope) + } + } } /// When inferring an expression, we propagate downward whatever type hint we diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs index 3293534a0..48c915302 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/coerce.rs @@ -50,11 +50,44 @@ fn success( #[derive(Clone, Debug)] pub(super) struct CoerceMany { expected_ty: Ty, + final_ty: Option<Ty>, } impl CoerceMany { pub(super) fn new(expected: Ty) -> Self { - CoerceMany { expected_ty: expected } + CoerceMany { expected_ty: expected, final_ty: None } + } + + /// Returns the "expected type" with which this coercion was + /// constructed. This represents the "downward propagated" type + /// that was given to us at the start of typing whatever construct + /// we are typing (e.g., the match expression). + /// + /// Typically, this is used as the expected type when + /// type-checking each of the alternative expressions whose types + /// we are trying to merge. + pub(super) fn expected_ty(&self) -> Ty { + self.expected_ty.clone() + } + + /// Returns the current "merged type", representing our best-guess + /// at the LUB of the expressions we've seen so far (if any). This + /// isn't *final* until you call `self.complete()`, which will return + /// the merged type. + pub(super) fn merged_ty(&self) -> Ty { + self.final_ty.clone().unwrap_or_else(|| self.expected_ty.clone()) + } + + pub(super) fn complete(self, ctx: &mut InferenceContext<'_>) -> Ty { + if let Some(final_ty) = self.final_ty { + final_ty + } else { + ctx.result.standard_types.never.clone() + } + } + + pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) { + self.coerce(ctx, None, &ctx.result.standard_types.unit.clone()) } /// Merge two types from different branches, with possible coercion. @@ -76,25 +109,25 @@ impl CoerceMany { // Special case: two function types. Try to coerce both to // pointers to have a chance at getting a match. See // https://github.com/rust-lang/rust/blob/7b805396bf46dce972692a6846ce2ad8481c5f85/src/librustc_typeck/check/coercion.rs#L877-L916 - let sig = match (self.expected_ty.kind(Interner), expr_ty.kind(Interner)) { + let sig = match (self.merged_ty().kind(Interner), expr_ty.kind(Interner)) { (TyKind::FnDef(..) | TyKind::Closure(..), TyKind::FnDef(..) | TyKind::Closure(..)) => { // FIXME: we're ignoring safety here. To be more correct, if we have one FnDef and one Closure, // we should be coercing the closure to a fn pointer of the safety of the FnDef cov_mark::hit!(coerce_fn_reification); let sig = - self.expected_ty.callable_sig(ctx.db).expect("FnDef without callable sig"); + self.merged_ty().callable_sig(ctx.db).expect("FnDef without callable sig"); Some(sig) } _ => None, }; if let Some(sig) = sig { let target_ty = TyKind::Function(sig.to_fn_ptr()).intern(Interner); - let result1 = ctx.table.coerce_inner(self.expected_ty.clone(), &target_ty); + let result1 = ctx.table.coerce_inner(self.merged_ty(), &target_ty); let result2 = ctx.table.coerce_inner(expr_ty.clone(), &target_ty); if let (Ok(result1), Ok(result2)) = (result1, result2) { ctx.table.register_infer_ok(result1); ctx.table.register_infer_ok(result2); - return self.expected_ty = target_ty; + return self.final_ty = Some(target_ty); } } @@ -102,25 +135,20 @@ impl CoerceMany { // type is a type variable and the new one is `!`, trying it the other // way around first would mean we make the type variable `!`, instead of // just marking it as possibly diverging. - if ctx.coerce(expr, &expr_ty, &self.expected_ty).is_ok() { - /* self.expected_ty is already correct */ - } else if ctx.coerce(expr, &self.expected_ty, &expr_ty).is_ok() { - self.expected_ty = expr_ty; + if let Ok(res) = ctx.coerce(expr, &expr_ty, &self.merged_ty()) { + self.final_ty = Some(res); + } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { + self.final_ty = Some(res); } else { if let Some(id) = expr { ctx.result.type_mismatches.insert( id.into(), - TypeMismatch { expected: self.expected_ty.clone(), actual: expr_ty }, + TypeMismatch { expected: self.merged_ty().clone(), actual: expr_ty.clone() }, ); } cov_mark::hit!(coerce_merge_fail_fallback); - /* self.expected_ty is already correct */ } } - - pub(super) fn complete(self) -> Ty { - self.expected_ty - } } pub fn could_coerce( @@ -665,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/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs index 175fded8c..ee186673e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/expr.rs @@ -15,7 +15,6 @@ use hir_def::{ generics::TypeOrConstParamData, lang_item::LangItem, path::{GenericArg, GenericArgs}, - resolver::resolver_for_expr, ConstParamId, FieldId, ItemContainerId, Lookup, }; use hir_expand::name::{name, Name}; @@ -25,7 +24,9 @@ use syntax::ast::RangeOp; use crate::{ autoderef::{self, Autoderef}, consteval, - infer::{coerce::CoerceMany, find_continuable, BreakableKind}, + infer::{ + coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, BreakableKind, + }, lower::{ const_or_path_to_chalk, generic_arg_to_chalk, lower_to_chalk_mutability, ParamLoweringMode, }, @@ -39,8 +40,8 @@ use crate::{ }; use super::{ - coerce::auto_deref_adjust_steps, find_breakable, BindingMode, BreakableContext, Diverges, - Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, + coerce::auto_deref_adjust_steps, find_breakable, BreakableContext, Diverges, Expectation, + InferenceContext, InferenceDiagnostic, TypeMismatch, }; impl<'a> InferenceContext<'a> { @@ -58,6 +59,10 @@ impl<'a> InferenceContext<'a> { ty } + pub(crate) fn infer_expr_no_expect(&mut self, tgt_expr: ExprId) -> Ty { + self.infer_expr_inner(tgt_expr, &Expectation::None) + } + /// Infer type of expression with possibly implicit coerce to the expected type. /// Return the type after possible coercion. pub(super) fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { @@ -78,6 +83,30 @@ impl<'a> InferenceContext<'a> { } } + pub(super) fn infer_expr_coerce_never(&mut self, expr: ExprId, expected: &Expectation) -> Ty { + let ty = self.infer_expr_inner(expr, expected); + // While we don't allow *arbitrary* coercions here, we *do* allow + // coercions from ! to `expected`. + if ty.is_never() { + if let Some(adjustments) = self.result.expr_adjustments.get(&expr) { + return if let [Adjustment { kind: Adjust::NeverToAny, target }] = &**adjustments { + target.clone() + } else { + self.err_ty() + }; + } + + let adj_ty = self.table.new_type_var(); + self.write_expr_adj( + expr, + vec![Adjustment { kind: Adjust::NeverToAny, target: adj_ty.clone() }], + ); + adj_ty + } else { + ty + } + } + fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { self.db.unwind_if_cancelled(); @@ -85,7 +114,7 @@ impl<'a> InferenceContext<'a> { Expr::Missing => self.err_ty(), &Expr::If { condition, then_branch, else_branch } => { let expected = &expected.adjust_for_branches(&mut self.table); - self.infer_expr( + self.infer_expr_coerce_never( condition, &Expectation::HasType(self.result.standard_types.bool_.clone()), ); @@ -97,59 +126,39 @@ impl<'a> InferenceContext<'a> { both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe); let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table)); coerce.coerce(self, Some(then_branch), &then_ty); - let else_ty = match else_branch { - Some(else_branch) => self.infer_expr_inner(else_branch, expected), - None => TyBuilder::unit(), - }; + match else_branch { + Some(else_branch) => { + let else_ty = self.infer_expr_inner(else_branch, expected); + coerce.coerce(self, Some(else_branch), &else_ty); + } + None => { + coerce.coerce_forced_unit(self); + } + } both_arms_diverge &= self.diverges; - // FIXME: create a synthetic `else {}` so we have something to refer to here instead of None? - coerce.coerce(self, else_branch, &else_ty); self.diverges = condition_diverges | both_arms_diverge; - coerce.complete() + coerce.complete(self) } &Expr::Let { pat, expr } => { let input_ty = self.infer_expr(expr, &Expectation::none()); - self.infer_pat(pat, &input_ty, BindingMode::default()); + self.infer_top_pat(pat, &input_ty); self.result.standard_types.bool_.clone() } Expr::Block { statements, tail, label, id: _ } => { - let old_resolver = mem::replace( - &mut self.resolver, - resolver_for_expr(self.db.upcast(), self.owner, tgt_expr), - ); - let ty = match label { - Some(_) => { - let break_ty = self.table.new_type_var(); - let (breaks, ty) = self.with_breakable_ctx( - BreakableKind::Block, - break_ty.clone(), - *label, - |this| { - this.infer_block( - tgt_expr, - statements, - *tail, - &Expectation::has_type(break_ty), - ) - }, - ); - breaks.unwrap_or(ty) - } - None => self.infer_block(tgt_expr, statements, *tail, expected), - }; - self.resolver = old_resolver; - ty + self.infer_block(tgt_expr, statements, *tail, *label, expected) + } + Expr::Unsafe { id: _, statements, tail } => { + self.infer_block(tgt_expr, statements, *tail, None, expected) } - Expr::Unsafe { body } => self.infer_expr(*body, expected), - Expr::Const { body } => { - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { - this.infer_expr(*body, expected) + Expr::Const { id: _, statements, tail } => { + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { + this.infer_block(tgt_expr, statements, *tail, None, expected) }) .1 } - Expr::TryBlock { body } => { + Expr::TryBlock { id: _, statements, tail } => { // The type that is returned from the try block let try_ty = self.table.new_type_var(); if let Some(ty) = expected.only_has_type(&mut self.table) { @@ -160,28 +169,41 @@ impl<'a> InferenceContext<'a> { let ok_ty = self.resolve_associated_type(try_ty.clone(), self.resolve_ops_try_output()); - self.with_breakable_ctx(BreakableKind::Block, ok_ty.clone(), None, |this| { - this.infer_expr(*body, &Expectation::has_type(ok_ty)); - }); - + self.infer_block( + tgt_expr, + statements, + *tail, + None, + &Expectation::has_type(ok_ty.clone()), + ); try_ty } - Expr::Async { body } => { + Expr::Async { id: _, statements, tail } => { let ret_ty = self.table.new_type_var(); let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_ret_coercion = + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); let (_, inner_ty) = - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { - this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)) + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { + this.infer_block( + tgt_expr, + statements, + *tail, + None, + &Expectation::has_type(ret_ty), + ) }); self.diverges = prev_diverges; self.return_ty = prev_ret_ty; + self.return_coercion = prev_ret_coercion; // Use the first type parameter as the output type of future. // existential type AsyncBlockImplTrait<InnerType>: Future<Output = InnerType> - let impl_trait_id = crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body); + let impl_trait_id = + crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, tgt_expr); let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); TyKind::OpaqueType(opaque_ty_id, Substitution::from1(Interner, inner_ty)) .intern(Interner) @@ -191,7 +213,7 @@ impl<'a> InferenceContext<'a> { // let ty = expected.coercion_target_type(&mut self.table); let ty = self.table.new_type_var(); let (breaks, ()) = - self.with_breakable_ctx(BreakableKind::Loop, ty, label, |this| { + self.with_breakable_ctx(BreakableKind::Loop, Some(ty), label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -204,7 +226,7 @@ impl<'a> InferenceContext<'a> { } } &Expr::While { condition, body, label } => { - self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { + self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { this.infer_expr( condition, &Expectation::HasType(this.result.standard_types.bool_.clone()), @@ -220,11 +242,13 @@ 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_pat(pat, &pat_ty, BindingMode::default()); - self.with_breakable_ctx(BreakableKind::Loop, self.err_ty(), label, |this| { + self.infer_top_pat(pat, &pat_ty); + self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { this.infer_expr(body, &Expectation::HasType(TyBuilder::unit())); }); @@ -251,7 +275,23 @@ impl<'a> InferenceContext<'a> { Some(type_ref) => self.make_ty(type_ref), None => self.table.new_type_var(), }; - sig_tys.push(ret_ty.clone()); + if let ClosureKind::Async = closure_kind { + // Use the first type parameter as the output type of future. + // existential type AsyncBlockImplTrait<InnerType>: Future<Output = InnerType> + let impl_trait_id = + crate::ImplTraitId::AsyncBlockTypeImplTrait(self.owner, *body); + let opaque_ty_id = self.db.intern_impl_trait_id(impl_trait_id).into(); + sig_tys.push( + TyKind::OpaqueType( + opaque_ty_id, + Substitution::from1(Interner, ret_ty.clone()), + ) + .intern(Interner), + ); + } else { + sig_tys.push(ret_ty.clone()); + } + let sig_ty = TyKind::Function(FnPointer { num_binders: 0, sig: FnSig { abi: (), safety: chalk_ir::Safety::Safe, variadic: false }, @@ -262,33 +302,38 @@ impl<'a> InferenceContext<'a> { }) .intern(Interner); - let (ty, resume_yield_tys) = if matches!(closure_kind, ClosureKind::Generator(_)) { - // FIXME: report error when there are more than 1 parameter. - let resume_ty = match sig_tys.first() { - // When `sig_tys.len() == 1` the first type is the return type, not the - // first parameter type. - Some(ty) if sig_tys.len() > 1 => ty.clone(), - _ => self.result.standard_types.unit.clone(), - }; - let yield_ty = self.table.new_type_var(); - - let subst = TyBuilder::subst_for_generator(self.db, self.owner) - .push(resume_ty.clone()) - .push(yield_ty.clone()) - .push(ret_ty.clone()) - .build(); + let (ty, resume_yield_tys) = match closure_kind { + ClosureKind::Generator(_) => { + // FIXME: report error when there are more than 1 parameter. + let resume_ty = match sig_tys.first() { + // When `sig_tys.len() == 1` the first type is the return type, not the + // first parameter type. + Some(ty) if sig_tys.len() > 1 => ty.clone(), + _ => self.result.standard_types.unit.clone(), + }; + let yield_ty = self.table.new_type_var(); + + let subst = TyBuilder::subst_for_generator(self.db, self.owner) + .push(resume_ty.clone()) + .push(yield_ty.clone()) + .push(ret_ty.clone()) + .build(); - let generator_id = self.db.intern_generator((self.owner, tgt_expr)).into(); - let generator_ty = TyKind::Generator(generator_id, subst).intern(Interner); + let generator_id = self.db.intern_generator((self.owner, tgt_expr)).into(); + let generator_ty = TyKind::Generator(generator_id, subst).intern(Interner); - (generator_ty, Some((resume_ty, yield_ty))) - } else { - let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into(); - let closure_ty = - TyKind::Closure(closure_id, Substitution::from1(Interner, sig_ty.clone())) - .intern(Interner); + (generator_ty, Some((resume_ty, yield_ty))) + } + ClosureKind::Closure | ClosureKind::Async => { + let closure_id = self.db.intern_closure((self.owner, tgt_expr)).into(); + let closure_ty = TyKind::Closure( + closure_id, + Substitution::from1(Interner, sig_ty.clone()), + ) + .intern(Interner); - (closure_ty, None) + (closure_ty, None) + } }; // Eagerly try to relate the closure type with the expected @@ -297,21 +342,25 @@ impl<'a> InferenceContext<'a> { self.deduce_closure_type_from_expectations(tgt_expr, &ty, &sig_ty, expected); // Now go through the argument patterns - for (arg_pat, arg_ty) in args.iter().zip(sig_tys) { - self.infer_pat(*arg_pat, &arg_ty, BindingMode::default()); + for (arg_pat, arg_ty) in args.iter().zip(&sig_tys) { + self.infer_top_pat(*arg_pat, &arg_ty); } + // FIXME: lift these out into a struct let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone()); + let prev_ret_coercion = + mem::replace(&mut self.return_coercion, Some(CoerceMany::new(ret_ty.clone()))); let prev_resume_yield_tys = mem::replace(&mut self.resume_yield_tys, resume_yield_tys); - self.with_breakable_ctx(BreakableKind::Border, self.err_ty(), None, |this| { - this.infer_expr_coerce(*body, &Expectation::has_type(ret_ty)); + self.with_breakable_ctx(BreakableKind::Border, None, None, |this| { + this.infer_return(*body); }); self.diverges = prev_diverges; self.return_ty = prev_ret_ty; + self.return_coercion = prev_ret_coercion; self.resume_yield_tys = prev_resume_yield_tys; ty @@ -348,7 +397,13 @@ impl<'a> InferenceContext<'a> { } (params, ret_ty) } - None => (Vec::new(), self.err_ty()), // FIXME diagnostic + None => { + self.result.diagnostics.push(InferenceDiagnostic::ExpectedFunction { + call_expr: tgt_expr, + found: callee_ty.clone(), + }); + (Vec::new(), self.err_ty()) + } }; let indices_to_skip = self.check_legacy_const_generics(derefed_callee, args); self.register_obligations_for_call(&callee_ty); @@ -381,92 +436,109 @@ impl<'a> InferenceContext<'a> { Expr::Match { expr, arms } => { let input_ty = self.infer_expr(*expr, &Expectation::none()); - let expected = expected.adjust_for_branches(&mut self.table); - - let result_ty = if arms.is_empty() { + if arms.is_empty() { + self.diverges = Diverges::Always; self.result.standard_types.never.clone() } else { - expected.coercion_target_type(&mut self.table) - }; - let mut coerce = CoerceMany::new(result_ty); - - let matchee_diverges = self.diverges; - let mut all_arms_diverge = Diverges::Always; - - for arm in arms.iter() { - self.diverges = Diverges::Maybe; - let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default()); - if let Some(guard_expr) = arm.guard { - self.infer_expr( - guard_expr, - &Expectation::HasType(self.result.standard_types.bool_.clone()), - ); + let matchee_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); + let mut all_arms_diverge = Diverges::Always; + for arm in arms.iter() { + let input_ty = self.resolve_ty_shallow(&input_ty); + self.infer_top_pat(arm.pat, &input_ty); } - let arm_ty = self.infer_expr_inner(arm.expr, &expected); - all_arms_diverge &= self.diverges; - coerce.coerce(self, Some(arm.expr), &arm_ty); - } + let expected = expected.adjust_for_branches(&mut self.table); + let result_ty = match &expected { + // We don't coerce to `()` so that if the match expression is a + // statement it's branches can have any consistent type. + Expectation::HasType(ty) if *ty != self.result.standard_types.unit => { + ty.clone() + } + _ => self.table.new_type_var(), + }; + let mut coerce = CoerceMany::new(result_ty); + + for arm in arms.iter() { + if let Some(guard_expr) = arm.guard { + self.diverges = Diverges::Maybe; + self.infer_expr_coerce_never( + guard_expr, + &Expectation::HasType(self.result.standard_types.bool_.clone()), + ); + } + self.diverges = Diverges::Maybe; - self.diverges = matchee_diverges | all_arms_diverge; + let arm_ty = self.infer_expr_inner(arm.expr, &expected); + all_arms_diverge &= self.diverges; + coerce.coerce(self, Some(arm.expr), &arm_ty); + } - coerce.complete() + self.diverges = matchee_diverges | all_arms_diverge; + + coerce.complete(self) + } } Expr::Path(p) => { - // FIXME this could be more efficient... - let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); - self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()) + let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, tgt_expr); + let ty = self.infer_path(p, tgt_expr.into()).unwrap_or_else(|| self.err_ty()); + self.resolver.reset_to_guard(g); + ty } Expr::Continue { label } => { if let None = find_continuable(&mut self.breakables, label.as_ref()) { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, is_break: false, + bad_value_break: false, }); }; self.result.standard_types.never.clone() } Expr::Break { expr, label } => { let val_ty = if let Some(expr) = *expr { - self.infer_expr(expr, &Expectation::none()) + let opt_coerce_to = match find_breakable(&mut self.breakables, label.as_ref()) { + Some(ctxt) => match &ctxt.coerce { + Some(coerce) => coerce.expected_ty(), + None => { + self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { + expr: tgt_expr, + is_break: true, + bad_value_break: true, + }); + self.err_ty() + } + }, + None => self.err_ty(), + }; + self.infer_expr_inner(expr, &Expectation::HasType(opt_coerce_to)) } else { TyBuilder::unit() }; match find_breakable(&mut self.breakables, label.as_ref()) { - Some(ctxt) => { - // avoiding the borrowck - let mut coerce = mem::replace( - &mut ctxt.coerce, - CoerceMany::new(expected.coercion_target_type(&mut self.table)), - ); - - // FIXME: create a synthetic `()` during lowering so we have something to refer to here? - coerce.coerce(self, *expr, &val_ty); - - let ctxt = find_breakable(&mut self.breakables, label.as_ref()) - .expect("breakable stack changed during coercion"); - ctxt.coerce = coerce; - ctxt.may_break = true; - } + Some(ctxt) => match ctxt.coerce.take() { + Some(mut coerce) => { + coerce.coerce(self, *expr, &val_ty); + + // Avoiding borrowck + let ctxt = find_breakable(&mut self.breakables, label.as_ref()) + .expect("breakable stack changed during coercion"); + ctxt.may_break = true; + ctxt.coerce = Some(coerce); + } + None => ctxt.may_break = true, + }, None => { self.push_diagnostic(InferenceDiagnostic::BreakOutsideOfLoop { expr: tgt_expr, is_break: true, + bad_value_break: false, }); } } self.result.standard_types.never.clone() } - Expr::Return { expr } => { - if let Some(expr) = expr { - self.infer_expr_coerce(*expr, &Expectation::has_type(self.return_ty.clone())); - } else { - let unit = TyBuilder::unit(); - let _ = self.coerce(Some(tgt_expr), &unit, &self.return_ty.clone()); - } - self.result.standard_types.never.clone() - } + &Expr::Return { expr } => self.infer_expr_return(expr), Expr::Yield { expr } => { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some(expr) = expr { @@ -483,7 +555,7 @@ impl<'a> InferenceContext<'a> { } Expr::Yeet { expr } => { if let &Some(expr) = expr { - self.infer_expr_inner(expr, &Expectation::None); + self.infer_expr_no_expect(expr); } self.result.standard_types.never.clone() } @@ -524,71 +596,7 @@ impl<'a> InferenceContext<'a> { } ty } - Expr::Field { expr, name } => { - let receiver_ty = self.infer_expr_inner(*expr, &Expectation::none()); - - let mut autoderef = Autoderef::new(&mut self.table, receiver_ty); - let mut private_field = None; - let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| { - let (field_id, parameters) = match derefed_ty.kind(Interner) { - TyKind::Tuple(_, substs) => { - return name.as_tuple_index().and_then(|idx| { - substs - .as_slice(Interner) - .get(idx) - .map(|a| a.assert_ty_ref(Interner)) - .cloned() - }); - } - TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => { - let local_id = self.db.struct_data(*s).variant_data.field(name)?; - let field = FieldId { parent: (*s).into(), local_id }; - (field, parameters.clone()) - } - TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => { - let local_id = self.db.union_data(*u).variant_data.field(name)?; - let field = FieldId { parent: (*u).into(), local_id }; - (field, parameters.clone()) - } - _ => return None, - }; - let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id] - .is_visible_from(self.db.upcast(), self.resolver.module()); - if !is_visible { - if private_field.is_none() { - private_field = Some(field_id); - } - return None; - } - // can't have `write_field_resolution` here because `self.table` is borrowed :( - self.result.field_resolutions.insert(tgt_expr, field_id); - let ty = self.db.field_types(field_id.parent)[field_id.local_id] - .clone() - .substitute(Interner, ¶meters); - Some(ty) - }); - let ty = match ty { - Some(ty) => { - let adjustments = auto_deref_adjust_steps(&autoderef); - self.write_expr_adj(*expr, adjustments); - let ty = self.insert_type_vars(ty); - let ty = self.normalize_associated_types_in(ty); - ty - } - _ => { - // Write down the first private field resolution if we found no field - // This aids IDE features for private fields like goto def - if let Some(field) = private_field { - self.result.field_resolutions.insert(tgt_expr, field); - self.result - .diagnostics - .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); - } - self.err_ty() - } - }; - ty - } + Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name), Expr::Await { expr } => { let inner_ty = self.infer_expr_inner(*expr, &Expectation::none()); self.resolve_associated_type(inner_ty, self.resolve_future_future_output()) @@ -611,7 +619,7 @@ impl<'a> InferenceContext<'a> { Expr::Cast { expr, type_ref } => { let cast_ty = self.make_ty(type_ref); // FIXME: propagate the "castable to" expectation - let _inner_ty = self.infer_expr_inner(*expr, &Expectation::None); + let _inner_ty = self.infer_expr_no_expect(*expr); // FIXME check the cast... cast_ty } @@ -807,49 +815,7 @@ impl<'a> InferenceContext<'a> { TyKind::Tuple(tys.len(), Substitution::from_iter(Interner, tys)).intern(Interner) } - Expr::Array(array) => { - let elem_ty = - match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(Interner)) { - Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(), - _ => self.table.new_type_var(), - }; - let mut coerce = CoerceMany::new(elem_ty.clone()); - - let expected = Expectation::has_type(elem_ty.clone()); - let len = match array { - Array::ElementList { elements, .. } => { - for &expr in elements.iter() { - let cur_elem_ty = self.infer_expr_inner(expr, &expected); - coerce.coerce(self, Some(expr), &cur_elem_ty); - } - consteval::usize_const(Some(elements.len() as u128)) - } - &Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty)); - self.infer_expr( - repeat, - &Expectation::HasType( - TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), - ), - ); - - if let Some(g_def) = self.owner.as_generic_def_id() { - let generics = generics(self.db.upcast(), g_def); - consteval::eval_to_const( - repeat, - ParamLoweringMode::Placeholder, - self, - || generics, - DebruijnIndex::INNERMOST, - ) - } else { - consteval::usize_const(None) - } - } - }; - - TyKind::Array(coerce.complete(), len).intern(Interner) - } + Expr::Array(array) => self.infer_expr_array(array, expected), Expr::Literal(lit) => match lit { Literal::Bool(..) => self.result.standard_types.bool_.clone(), Literal::String(..) => { @@ -859,7 +825,11 @@ impl<'a> InferenceContext<'a> { Literal::ByteString(bs) => { let byte_type = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); - let len = consteval::usize_const(Some(bs.len() as u128)); + let len = consteval::usize_const( + self.db, + Some(bs.len() as u128), + self.resolver.krate(), + ); let array_type = TyKind::Array(byte_type, len).intern(Interner); TyKind::Ref(Mutability::Not, static_lifetime(), array_type).intern(Interner) @@ -904,6 +874,97 @@ impl<'a> InferenceContext<'a> { ty } + fn infer_expr_array( + &mut self, + array: &Array, + expected: &Expectation, + ) -> chalk_ir::Ty<Interner> { + let elem_ty = match expected.to_option(&mut self.table).as_ref().map(|t| t.kind(Interner)) { + Some(TyKind::Array(st, _) | TyKind::Slice(st)) => st.clone(), + _ => self.table.new_type_var(), + }; + + let krate = self.resolver.krate(); + + let expected = Expectation::has_type(elem_ty.clone()); + let (elem_ty, len) = match array { + Array::ElementList { elements, .. } if elements.is_empty() => { + (elem_ty, consteval::usize_const(self.db, Some(0), krate)) + } + Array::ElementList { elements, .. } => { + let mut coerce = CoerceMany::new(elem_ty.clone()); + for &expr in elements.iter() { + let cur_elem_ty = self.infer_expr_inner(expr, &expected); + coerce.coerce(self, Some(expr), &cur_elem_ty); + } + ( + coerce.complete(self), + consteval::usize_const(self.db, Some(elements.len() as u128), krate), + ) + } + &Array::Repeat { initializer, repeat } => { + self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty.clone())); + self.infer_expr( + repeat, + &Expectation::HasType( + TyKind::Scalar(Scalar::Uint(UintTy::Usize)).intern(Interner), + ), + ); + + ( + elem_ty, + if let Some(g_def) = self.owner.as_generic_def_id() { + let generics = generics(self.db.upcast(), g_def); + consteval::eval_to_const( + repeat, + ParamLoweringMode::Placeholder, + self, + || generics, + DebruijnIndex::INNERMOST, + ) + } else { + consteval::usize_const(self.db, None, krate) + }, + ) + } + }; + + TyKind::Array(elem_ty, len).intern(Interner) + } + + pub(super) fn infer_return(&mut self, expr: ExprId) { + let ret_ty = self + .return_coercion + .as_mut() + .expect("infer_return called outside function body") + .expected_ty(); + let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); + let mut coerce_many = self.return_coercion.take().unwrap(); + coerce_many.coerce(self, Some(expr), &return_expr_ty); + self.return_coercion = Some(coerce_many); + } + + fn infer_expr_return(&mut self, expr: Option<ExprId>) -> Ty { + match self.return_coercion { + Some(_) => { + if let Some(expr) = expr { + self.infer_return(expr); + } else { + let mut coerce = self.return_coercion.take().unwrap(); + coerce.coerce_forced_unit(self); + self.return_coercion = Some(coerce); + } + } + None => { + // FIXME: diagnose return outside of function + if let Some(expr) = expr { + self.infer_expr_no_expect(expr); + } + } + } + self.result.standard_types.never.clone() + } + fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty { if let Some(box_id) = self.resolve_boxed_box() { let table = &mut self.table; @@ -982,8 +1043,11 @@ impl<'a> InferenceContext<'a> { // type and length). This should not be just an error type, // because we are to compute the unifiability of this type and // `rhs_ty` in the end of this function to issue type mismatches. - _ => TyKind::Array(self.err_ty(), crate::consteval::usize_const(None)) - .intern(Interner), + _ => TyKind::Array( + self.err_ty(), + crate::consteval::usize_const(self.db, None, self.resolver.krate()), + ) + .intern(Interner), } } Expr::RecordLit { path, fields, .. } => { @@ -1123,65 +1187,211 @@ impl<'a> InferenceContext<'a> { expr: ExprId, statements: &[Statement], tail: Option<ExprId>, + label: Option<LabelId>, expected: &Expectation, ) -> Ty { - for stmt in statements { - match stmt { - Statement::Let { pat, type_ref, initializer, else_branch } => { - let decl_ty = type_ref - .as_ref() - .map(|tr| self.make_ty(tr)) - .unwrap_or_else(|| self.err_ty()); - - // Always use the declared type when specified - let mut ty = decl_ty.clone(); - - if let Some(expr) = initializer { - let actual_ty = - self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())); - if decl_ty.is_unknown() { - ty = actual_ty; + let coerce_ty = expected.coercion_target_type(&mut self.table); + let g = self.resolver.update_to_inner_scope(self.db.upcast(), self.owner, expr); + + let (break_ty, ty) = + self.with_breakable_ctx(BreakableKind::Block, Some(coerce_ty.clone()), label, |this| { + for stmt in statements { + match stmt { + Statement::Let { pat, type_ref, initializer, else_branch } => { + let decl_ty = type_ref + .as_ref() + .map(|tr| this.make_ty(tr)) + .unwrap_or_else(|| this.table.new_type_var()); + + let ty = if let Some(expr) = initializer { + let ty = if contains_explicit_ref_binding(&this.body, *pat) { + this.infer_expr(*expr, &Expectation::has_type(decl_ty.clone())) + } else { + this.infer_expr_coerce( + *expr, + &Expectation::has_type(decl_ty.clone()), + ) + }; + if type_ref.is_some() { + decl_ty + } else { + ty + } + } else { + decl_ty + }; + + this.infer_top_pat(*pat, &ty); + + if let Some(expr) = else_branch { + let previous_diverges = + mem::replace(&mut this.diverges, Diverges::Maybe); + this.infer_expr_coerce( + *expr, + &Expectation::HasType(this.result.standard_types.never.clone()), + ); + this.diverges = previous_diverges; + } + } + &Statement::Expr { expr, has_semi } => { + if has_semi { + this.infer_expr(expr, &Expectation::none()); + } else { + this.infer_expr_coerce( + expr, + &Expectation::HasType(this.result.standard_types.unit.clone()), + ); + } } } + } - if let Some(expr) = else_branch { - self.infer_expr_coerce( - *expr, - &Expectation::HasType(self.result.standard_types.never.clone()), - ); + // FIXME: This should make use of the breakable CoerceMany + if let Some(expr) = tail { + this.infer_expr_coerce(expr, expected) + } else { + // Citing rustc: if there is no explicit tail expression, + // that is typically equivalent to a tail expression + // of `()` -- except if the block diverges. In that + // case, there is no value supplied from the tail + // expression (assuming there are no other breaks, + // this implies that the type of the block will be + // `!`). + if this.diverges.is_always() { + // we don't even make an attempt at coercion + this.table.new_maybe_never_var() + } else if let Some(t) = expected.only_has_type(&mut this.table) { + if this + .coerce(Some(expr), &this.result.standard_types.unit.clone(), &t) + .is_err() + { + this.result.type_mismatches.insert( + expr.into(), + TypeMismatch { + expected: t.clone(), + actual: this.result.standard_types.unit.clone(), + }, + ); + } + t + } else { + this.result.standard_types.unit.clone() } + } + }); + self.resolver.reset_to_guard(g); + + break_ty.unwrap_or(ty) + } - self.infer_pat(*pat, &ty, BindingMode::default()); + fn lookup_field( + &mut self, + receiver_ty: &Ty, + name: &Name, + ) -> Option<(Ty, Option<FieldId>, Vec<Adjustment>, bool)> { + let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone()); + let mut private_field = None; + let res = autoderef.by_ref().find_map(|(derefed_ty, _)| { + let (field_id, parameters) = match derefed_ty.kind(Interner) { + TyKind::Tuple(_, substs) => { + return name.as_tuple_index().and_then(|idx| { + substs + .as_slice(Interner) + .get(idx) + .map(|a| a.assert_ty_ref(Interner)) + .cloned() + .map(|ty| (None, ty)) + }); + } + TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => { + let local_id = self.db.struct_data(*s).variant_data.field(name)?; + let field = FieldId { parent: (*s).into(), local_id }; + (field, parameters.clone()) } - Statement::Expr { expr, .. } => { - self.infer_expr(*expr, &Expectation::none()); + TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => { + let local_id = self.db.union_data(*u).variant_data.field(name)?; + let field = FieldId { parent: (*u).into(), local_id }; + (field, parameters.clone()) } + _ => return None, + }; + let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id] + .is_visible_from(self.db.upcast(), self.resolver.module()); + if !is_visible { + if private_field.is_none() { + private_field = Some((field_id, parameters)); + } + return None; } - } + let ty = self.db.field_types(field_id.parent)[field_id.local_id] + .clone() + .substitute(Interner, ¶meters); + Some((Some(field_id), ty)) + }); - if let Some(expr) = tail { - self.infer_expr_coerce(expr, expected) - } else { - // Citing rustc: if there is no explicit tail expression, - // that is typically equivalent to a tail expression - // of `()` -- except if the block diverges. In that - // case, there is no value supplied from the tail - // expression (assuming there are no other breaks, - // this implies that the type of the block will be - // `!`). - if self.diverges.is_always() { - // we don't even make an attempt at coercion - self.table.new_maybe_never_var() - } else if let Some(t) = expected.only_has_type(&mut self.table) { - if self.coerce(Some(expr), &TyBuilder::unit(), &t).is_err() { - self.result.type_mismatches.insert( - expr.into(), - TypeMismatch { expected: t.clone(), actual: TyBuilder::unit() }, - ); + Some(match res { + Some((field_id, ty)) => { + let adjustments = auto_deref_adjust_steps(&autoderef); + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + + (ty, field_id, adjustments, true) + } + None => { + let (field_id, subst) = private_field?; + let adjustments = auto_deref_adjust_steps(&autoderef); + let ty = self.db.field_types(field_id.parent)[field_id.local_id] + .clone() + .substitute(Interner, &subst); + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + + (ty, Some(field_id), adjustments, false) + } + }) + } + + fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name) -> Ty { + let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none()); + match self.lookup_field(&receiver_ty, name) { + Some((ty, field_id, adjustments, is_public)) => { + self.write_expr_adj(receiver, adjustments); + if let Some(field_id) = field_id { + self.result.field_resolutions.insert(tgt_expr, field_id); } - t - } else { - TyBuilder::unit() + if !is_public { + if let Some(field) = field_id { + // FIXME: Merge this diagnostic into UnresolvedField? + self.result + .diagnostics + .push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field }); + } + } + ty + } + None => { + // no field found, + let method_with_same_name_exists = { + self.get_traits_in_scope(); + + let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); + method_resolution::lookup_method( + self.db, + &canonicalized_receiver.value, + self.trait_env.clone(), + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), + VisibleFromModule::Filter(self.resolver.module()), + name, + ) + .is_some() + }; + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField { + expr: tgt_expr, + receiver: receiver_ty, + name: name.clone(), + method_with_same_name_exists, + }); + self.err_ty() } } } @@ -1198,13 +1408,11 @@ impl<'a> InferenceContext<'a> { let receiver_ty = self.infer_expr(receiver, &Expectation::none()); let canonicalized_receiver = self.canonicalize(receiver_ty.clone()); - let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); - let resolved = method_resolution::lookup_method( self.db, &canonicalized_receiver.value, self.trait_env.clone(), - &traits_in_scope, + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), method_name, ); @@ -1223,11 +1431,30 @@ impl<'a> InferenceContext<'a> { } (ty, self.db.value_ty(func.into()), substs) } - None => ( - receiver_ty, - Binders::empty(Interner, self.err_ty()), - Substitution::empty(Interner), - ), + None => { + let field_with_same_name_exists = match self.lookup_field(&receiver_ty, method_name) + { + Some((ty, field_id, adjustments, _public)) => { + self.write_expr_adj(receiver, adjustments); + if let Some(field_id) = field_id { + self.result.field_resolutions.insert(tgt_expr, field_id); + } + Some(ty) + } + None => None, + }; + self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall { + expr: tgt_expr, + receiver: receiver_ty.clone(), + name: method_name.clone(), + field_with_same_name: field_with_same_name_exists, + }); + ( + receiver_ty, + Binders::empty(Interner, self.err_ty()), + Substitution::empty(Interner), + ) + } }; let method_ty = method_ty.substitute(Interner, &substs); self.register_obligations_for_call(&method_ty); @@ -1636,16 +1863,16 @@ impl<'a> InferenceContext<'a> { fn with_breakable_ctx<T>( &mut self, kind: BreakableKind, - ty: Ty, + ty: Option<Ty>, label: Option<LabelId>, cb: impl FnOnce(&mut Self) -> T, ) -> (Option<Ty>, T) { self.breakables.push({ let label = label.map(|label| self.body[label].name.clone()); - BreakableContext { kind, may_break: false, coerce: CoerceMany::new(ty), label } + BreakableContext { kind, may_break: false, coerce: ty.map(CoerceMany::new), label } }); let res = cb(self); let ctx = self.breakables.pop().expect("breakable stack broken"); - (ctx.may_break.then(|| ctx.coerce.complete()), res) + (if ctx.may_break { ctx.coerce.map(|ctx| ctx.complete(self)) } else { None }, res) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs index f154dac8e..5f839fc30 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/pat.rs @@ -4,22 +4,57 @@ use std::iter::repeat_with; use chalk_ir::Mutability; use hir_def::{ - expr::{BindingAnnotation, Expr, Literal, Pat, PatId}, + body::Body, + expr::{Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId}, path::Path, - type_ref::ConstScalar, }; use hir_expand::name::Name; use crate::{ - consteval::intern_const_scalar, + consteval::{try_const_usize, usize_const}, infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, lower::lower_to_chalk_mutability, primitive::UintTy, - static_lifetime, ConcreteConst, ConstValue, Interner, Scalar, Substitution, Ty, TyBuilder, - TyExt, TyKind, + static_lifetime, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, TyKind, }; -use super::PatLike; +/// Used to generalize patterns and assignee expressions. +pub(super) trait PatLike: Into<ExprOrPatId> + Copy { + type BindingMode: Copy; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty; +} + +impl PatLike for ExprId { + type BindingMode = (); + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + (): Self::BindingMode, + ) -> Ty { + this.infer_assignee_expr(id, expected_ty) + } +} + +impl PatLike for PatId { + type BindingMode = BindingMode; + + fn infer( + this: &mut InferenceContext<'_>, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty { + this.infer_pat(id, expected_ty, default_bm) + } +} impl<'a> InferenceContext<'a> { /// Infers type for tuple struct pattern or its corresponding assignee expression. @@ -112,6 +147,7 @@ impl<'a> InferenceContext<'a> { ellipsis: Option<usize>, subs: &[T], ) -> Ty { + let expected = self.resolve_ty_shallow(expected); let expectations = match expected.as_tuple() { Some(parameters) => &*parameters.as_slice(Interner), _ => &[], @@ -145,12 +181,11 @@ impl<'a> InferenceContext<'a> { .intern(Interner) } - pub(super) fn infer_pat( - &mut self, - pat: PatId, - expected: &Ty, - mut default_bm: BindingMode, - ) -> Ty { + pub(super) fn infer_top_pat(&mut self, pat: PatId, expected: &Ty) { + self.infer_pat(pat, expected, BindingMode::default()); + } + + fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { let mut expected = self.resolve_ty_shallow(expected); if is_non_ref_pat(self.body, pat) { @@ -185,30 +220,17 @@ impl<'a> InferenceContext<'a> { self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) } Pat::Or(pats) => { - if let Some((first_pat, rest)) = pats.split_first() { - let ty = self.infer_pat(*first_pat, &expected, default_bm); - for pat in rest { - self.infer_pat(*pat, &expected, default_bm); - } - ty - } else { - self.err_ty() + for pat in pats.iter() { + self.infer_pat(*pat, &expected, default_bm); } + expected.clone() } - Pat::Ref { pat, mutability } => { - let mutability = lower_to_chalk_mutability(*mutability); - let expectation = match expected.as_reference() { - Some((inner_ty, _lifetime, exp_mut)) => { - if mutability != exp_mut { - // FIXME: emit type error? - } - inner_ty.clone() - } - _ => self.result.standard_types.unknown.clone(), - }; - let subty = self.infer_pat(*pat, &expectation, default_bm); - TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) - } + &Pat::Ref { pat, mutability } => self.infer_ref_pat( + pat, + lower_to_chalk_mutability(mutability), + &expected, + default_bm, + ), Pat::TupleStruct { path: p, args: subpats, ellipsis } => self .infer_tuple_struct_pat_like( p.as_deref(), @@ -223,72 +245,14 @@ impl<'a> InferenceContext<'a> { self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat, subs) } Pat::Path(path) => { - // FIXME use correct resolver for the surrounding expression - let resolver = self.resolver.clone(); - self.infer_path(&resolver, path, pat.into()).unwrap_or_else(|| self.err_ty()) + // FIXME update resolver for the surrounding expression + self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty()) } - Pat::Bind { mode, name: _, subpat } => { - let mode = if mode == &BindingAnnotation::Unannotated { - default_bm - } else { - BindingMode::convert(*mode) - }; - self.result.pat_binding_modes.insert(pat, mode); - - let inner_ty = match subpat { - Some(subpat) => self.infer_pat(*subpat, &expected, default_bm), - None => expected, - }; - let inner_ty = self.insert_type_vars_shallow(inner_ty); - - let bound_ty = match mode { - BindingMode::Ref(mutability) => { - TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()) - .intern(Interner) - } - BindingMode::Move => inner_ty.clone(), - }; - self.write_pat_ty(pat, bound_ty); - return inner_ty; + Pat::Bind { id, subpat } => { + return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected); } Pat::Slice { prefix, slice, suffix } => { - let elem_ty = match expected.kind(Interner) { - TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), - _ => self.err_ty(), - }; - - for &pat_id in prefix.iter().chain(suffix.iter()) { - self.infer_pat(pat_id, &elem_ty, default_bm); - } - - if let &Some(slice_pat_id) = slice { - let rest_pat_ty = match expected.kind(Interner) { - TyKind::Array(_, length) => { - let len = match length.data(Interner).value { - ConstValue::Concrete(ConcreteConst { - interned: ConstScalar::UInt(len), - }) => len.checked_sub((prefix.len() + suffix.len()) as u128), - _ => None, - }; - TyKind::Array( - elem_ty.clone(), - intern_const_scalar( - len.map_or(ConstScalar::Unknown, |len| ConstScalar::UInt(len)), - TyBuilder::usize(), - ), - ) - } - _ => TyKind::Slice(elem_ty.clone()), - } - .intern(Interner); - self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); - } - - match expected.kind(Interner) { - TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), - _ => TyKind::Slice(elem_ty), - } - .intern(Interner) + self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) } Pat::Wild => expected.clone(), Pat::Range { start, end } => { @@ -296,27 +260,10 @@ impl<'a> InferenceContext<'a> { self.infer_expr(*end, &Expectation::has_type(start_ty)) } &Pat::Lit(expr) => { - // FIXME: using `Option` here is a workaround until we can use if-let chains in stable. - let mut pat_ty = None; - - // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. - if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { - if let Some((inner, ..)) = expected.as_reference() { - let inner = self.resolve_ty_shallow(inner); - if matches!(inner.kind(Interner), TyKind::Slice(_)) { - let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); - let slice_ty = TyKind::Slice(elem_ty).intern(Interner); - let ty = TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty) - .intern(Interner); - self.write_expr_ty(expr, ty.clone()); - pat_ty = Some(ty); - } - } - } - - pat_ty.unwrap_or_else(|| { - self.infer_expr(expr, &Expectation::has_type(expected.clone())) - }) + // Don't emit type mismatches again, the expression lowering already did that. + let ty = self.infer_lit_pat(expr, &expected); + self.write_pat_ty(pat, ty.clone()); + return ty; } Pat::Box { inner } => match self.resolve_boxed_box() { Some(box_adt) => { @@ -345,7 +292,8 @@ impl<'a> InferenceContext<'a> { }; // use a new type variable if we got error type here let ty = self.insert_type_vars_shallow(ty); - if !self.unify(&ty, &expected) { + // FIXME: This never check is odd, but required with out we do inference right now + if !expected.is_never() && !self.unify(&ty, &expected) { self.result .type_mismatches .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); @@ -353,6 +301,111 @@ impl<'a> InferenceContext<'a> { self.write_pat_ty(pat, ty.clone()); ty } + + fn infer_ref_pat( + &mut self, + pat: PatId, + mutability: Mutability, + expected: &Ty, + default_bm: BindingMode, + ) -> Ty { + let expectation = match expected.as_reference() { + Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), + _ => self.result.standard_types.unknown.clone(), + }; + let subty = self.infer_pat(pat, &expectation, default_bm); + TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) + } + + fn infer_bind_pat( + &mut self, + pat: PatId, + 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 { + BindingMode::convert(mode) + }; + self.result.pat_binding_modes.insert(pat, mode); + + let inner_ty = match subpat { + Some(subpat) => self.infer_pat(subpat, &expected, default_bm), + None => expected.clone(), + }; + let inner_ty = self.insert_type_vars_shallow(inner_ty); + + let bound_ty = match mode { + BindingMode::Ref(mutability) => { + TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner) + } + BindingMode::Move => inner_ty.clone(), + }; + self.write_pat_ty(pat, bound_ty.clone()); + self.write_binding_ty(binding, bound_ty); + return inner_ty; + } + + fn infer_slice_pat( + &mut self, + expected: &Ty, + prefix: &[PatId], + slice: &Option<PatId>, + suffix: &[PatId], + default_bm: BindingMode, + ) -> Ty { + let elem_ty = match expected.kind(Interner) { + TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), + _ => self.err_ty(), + }; + + for &pat_id in prefix.iter().chain(suffix.iter()) { + self.infer_pat(pat_id, &elem_ty, default_bm); + } + + if let &Some(slice_pat_id) = slice { + let rest_pat_ty = match expected.kind(Interner) { + TyKind::Array(_, length) => { + let len = try_const_usize(length); + let len = + len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); + TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) + } + _ => TyKind::Slice(elem_ty.clone()), + } + .intern(Interner); + self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); + } + + match expected.kind(Interner) { + TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), + _ => TyKind::Slice(elem_ty), + } + .intern(Interner) + } + + fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty { + // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. + if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { + if let Some((inner, ..)) = expected.as_reference() { + let inner = self.resolve_ty_shallow(inner); + if matches!(inner.kind(Interner), TyKind::Slice(_)) { + let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); + let slice_ty = TyKind::Slice(elem_ty).intern(Interner); + let ty = + TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner); + self.write_expr_ty(expr, ty.clone()); + return ty; + } + } + } + + self.infer_expr(expr, &Expectation::has_type(expected.clone())) + } } fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool { @@ -369,11 +422,22 @@ 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, } } + +pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { + let mut res = false; + body.walk_pats(pat_id, &mut |pat| { + res |= matches!(pat, Pat::Bind { id, .. } if body.bindings[*id].mode == BindingAnnotation::Ref); + }); + res +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs index 0a8527afb..2267fedaa 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/path.rs @@ -3,7 +3,7 @@ use chalk_ir::cast::Cast; use hir_def::{ path::{Path, PathSegment}, - resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, + resolver::{ResolveValueResult, TypeNs, ValueNs}, AdtId, AssocItemId, EnumVariantId, ItemContainerId, Lookup, }; use hir_expand::name::Name; @@ -21,55 +21,42 @@ use crate::{ use super::{ExprOrPatId, InferenceContext, TraitRef}; impl<'a> InferenceContext<'a> { - pub(super) fn infer_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option<Ty> { - let ty = self.resolve_value_path(resolver, path, id)?; + pub(super) fn infer_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> { + let ty = self.resolve_value_path(path, id)?; let ty = self.insert_type_vars(ty); let ty = self.normalize_associated_types_in(ty); Some(ty) } - fn resolve_value_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option<Ty> { + fn resolve_value_path(&mut self, path: &Path, id: ExprOrPatId) -> Option<Ty> { let (value, self_subst) = if let Some(type_ref) = path.type_anchor() { - if path.segments().is_empty() { - // This can't actually happen syntax-wise - return None; - } + let Some(last) = path.segments().last() else { return None }; let ty = self.make_ty(type_ref); let remaining_segments_for_ty = path.segments().take(path.segments().len() - 1); - let ctx = crate::lower::TyLoweringContext::new(self.db, resolver); + let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); let (ty, _) = ctx.lower_ty_relative_path(ty, None, remaining_segments_for_ty); - self.resolve_ty_assoc_item( - ty, - path.segments().last().expect("path had at least one segment").name, - id, - )? + self.resolve_ty_assoc_item(ty, last.name, id).map(|(it, substs)| (it, Some(substs)))? } else { + // FIXME: report error, unresolved first path segment let value_or_partial = - resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; + self.resolver.resolve_path_in_value_ns(self.db.upcast(), path.mod_path())?; match value_or_partial { ResolveValueResult::ValueNs(it) => (it, None), - ResolveValueResult::Partial(def, remaining_index) => { - self.resolve_assoc_item(def, path, remaining_index, id)? - } + ResolveValueResult::Partial(def, remaining_index) => self + .resolve_assoc_item(def, path, remaining_index, id) + .map(|(it, substs)| (it, Some(substs)))?, } }; let typable: ValueTyDefId = match value { - ValueNs::LocalBinding(pat) => { - let ty = self.result.type_of_pat.get(pat)?.clone(); - return Some(ty); - } + ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) { + Some(ty) => return Some(ty.clone()), + None => { + never!("uninferred pattern?"); + return None; + } + }, ValueNs::FunctionId(it) => it.into(), ValueNs::ConstId(it) => it.into(), ValueNs::StaticId(it) => it.into(), @@ -91,7 +78,7 @@ impl<'a> InferenceContext<'a> { let ty = self.db.value_ty(struct_id.into()).substitute(Interner, &substs); return Some(ty); } else { - // FIXME: diagnostic, invalid Self reference + // FIXME: report error, invalid Self reference return None; } } @@ -126,7 +113,7 @@ impl<'a> InferenceContext<'a> { path: &Path, remaining_index: usize, id: ExprOrPatId, - ) -> Option<(ValueNs, Option<Substitution>)> { + ) -> Option<(ValueNs, Substitution)> { assert!(remaining_index < path.segments().len()); // there may be more intermediate segments between the resolved one and // the end. Only the last segment needs to be resolved to a value; from @@ -179,7 +166,7 @@ impl<'a> InferenceContext<'a> { trait_ref: TraitRef, segment: PathSegment<'_>, id: ExprOrPatId, - ) -> Option<(ValueNs, Option<Substitution>)> { + ) -> Option<(ValueNs, Substitution)> { let trait_ = trait_ref.hir_trait_id(); let item = self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id)).find_map(|item| { @@ -215,7 +202,7 @@ impl<'a> InferenceContext<'a> { }; self.write_assoc_resolution(id, item, trait_ref.substitution.clone()); - Some((def, Some(trait_ref.substitution))) + Some((def, trait_ref.substitution)) } fn resolve_ty_assoc_item( @@ -223,7 +210,7 @@ impl<'a> InferenceContext<'a> { ty: Ty, name: &Name, id: ExprOrPatId, - ) -> Option<(ValueNs, Option<Substitution>)> { + ) -> Option<(ValueNs, Substitution)> { if let TyKind::Error = ty.kind(Interner) { return None; } @@ -233,70 +220,66 @@ impl<'a> InferenceContext<'a> { } let canonical_ty = self.canonicalize(ty.clone()); - let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast()); let mut not_visible = None; let res = method_resolution::iterate_method_candidates( &canonical_ty.value, self.db, self.table.trait_env.clone(), - &traits_in_scope, + self.get_traits_in_scope().as_ref().left_or_else(|&it| it), VisibleFromModule::Filter(self.resolver.module()), Some(name), method_resolution::LookupMode::Path, |_ty, item, visible| { - let (def, container) = match item { - AssocItemId::FunctionId(f) => { - (ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container) - } - AssocItemId::ConstId(c) => { - (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container) - } - AssocItemId::TypeAliasId(_) => unreachable!(), - }; - let substs = match container { - ItemContainerId::ImplId(impl_id) => { - let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None) - .fill_with_inference_vars(&mut self.table) - .build(); - let impl_self_ty = - self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs); - self.unify(&impl_self_ty, &ty); - impl_substs - } - ItemContainerId::TraitId(trait_) => { - // we're picking this method - let trait_ref = TyBuilder::trait_ref(self.db, trait_) - .push(ty.clone()) - .fill_with_inference_vars(&mut self.table) - .build(); - self.push_obligation(trait_ref.clone().cast(Interner)); - trait_ref.substitution - } - ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { - never!("assoc item contained in module/extern block"); - return None; - } - }; - if visible { - Some((def, item, Some(substs), true)) + Some((item, true)) } else { if not_visible.is_none() { - not_visible = Some((def, item, Some(substs), false)); + not_visible = Some((item, false)); } None } }, ); let res = res.or(not_visible); - if let Some((_, item, Some(ref substs), visible)) = res { - self.write_assoc_resolution(id, item, substs.clone()); - if !visible { - self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }) + let (item, visible) = res?; + + let (def, container) = match item { + AssocItemId::FunctionId(f) => { + (ValueNs::FunctionId(f), f.lookup(self.db.upcast()).container) + } + AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db.upcast()).container), + AssocItemId::TypeAliasId(_) => unreachable!(), + }; + let substs = match container { + ItemContainerId::ImplId(impl_id) => { + let impl_substs = TyBuilder::subst_for_def(self.db, impl_id, None) + .fill_with_inference_vars(&mut self.table) + .build(); + let impl_self_ty = self.db.impl_self_ty(impl_id).substitute(Interner, &impl_substs); + self.unify(&impl_self_ty, &ty); + impl_substs } + ItemContainerId::TraitId(trait_) => { + // we're picking this method + let trait_ref = TyBuilder::trait_ref(self.db, trait_) + .push(ty.clone()) + .fill_with_inference_vars(&mut self.table) + .build(); + self.push_obligation(trait_ref.clone().cast(Interner)); + trait_ref.substitution + } + ItemContainerId::ModuleId(_) | ItemContainerId::ExternBlockId(_) => { + never!("assoc item contained in module/extern block"); + return None; + } + }; + + self.write_assoc_resolution(id, item, substs.clone()); + if !visible { + self.push_diagnostic(InferenceDiagnostic::PrivateAssocItem { id, item }); } - res.map(|(def, _, substs, _)| (def, substs)) + Some((def, substs)) } fn resolve_enum_variant_on_ty( @@ -304,7 +287,7 @@ impl<'a> InferenceContext<'a> { ty: &Ty, name: &Name, id: ExprOrPatId, - ) -> Option<(ValueNs, Option<Substitution>)> { + ) -> Option<(ValueNs, Substitution)> { let ty = self.resolve_ty_shallow(ty); let (enum_id, subst) = match ty.as_adt() { Some((AdtId::EnumId(e), subst)) => (e, subst), @@ -314,6 +297,6 @@ impl<'a> InferenceContext<'a> { let local_id = enum_data.variant(name)?; let variant = EnumVariantId { parent: enum_id, local_id }; self.write_variant_resolution(id, variant.into()); - Some((ValueNs::EnumVariantId(variant), Some(subst.clone()))) + Some((ValueNs::EnumVariantId(variant), subst.clone())) } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs index 46ed3533c..504f0743a 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/infer/unify.rs @@ -704,14 +704,13 @@ impl<'a> fmt::Debug for InferenceTable<'a> { mod resolve { use super::InferenceTable; use crate::{ - ConcreteConst, Const, ConstData, ConstValue, DebruijnIndex, GenericArg, InferenceVar, - Interner, Lifetime, Ty, TyVariableKind, VariableKind, + ConcreteConst, Const, ConstData, ConstScalar, ConstValue, DebruijnIndex, GenericArg, + InferenceVar, Interner, Lifetime, Ty, TyVariableKind, VariableKind, }; use chalk_ir::{ cast::Cast, fold::{TypeFoldable, TypeFolder}, }; - use hir_def::type_ref::ConstScalar; #[derive(chalk_derive::FallibleTypeFolder)] #[has_interner(Interner)] diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs b/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs index 0c547192a..36af78153 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/inhabitedness.rs @@ -6,12 +6,12 @@ use chalk_ir::{ DebruijnIndex, }; use hir_def::{ - adt::VariantData, attr::Attrs, type_ref::ConstScalar, visibility::Visibility, AdtId, - EnumVariantId, HasModule, Lookup, ModuleId, VariantId, + adt::VariantData, attr::Attrs, visibility::Visibility, AdtId, EnumVariantId, HasModule, Lookup, + ModuleId, VariantId, }; use crate::{ - db::HirDatabase, Binders, ConcreteConst, Const, ConstValue, Interner, Substitution, Ty, TyKind, + consteval::try_const_usize, db::HirDatabase, Binders, Interner, Substitution, Ty, TyKind, }; /// Checks whether a type is visibly uninhabited from a particular module. @@ -69,7 +69,7 @@ impl TypeVisitor<Interner> for UninhabitedFrom<'_> { TyKind::Adt(adt, subst) => self.visit_adt(adt.0, subst), TyKind::Never => BREAK_VISIBLY_UNINHABITED, TyKind::Tuple(..) => ty.super_visit_with(self, outer_binder), - TyKind::Array(item_ty, len) => match try_usize_const(len) { + TyKind::Array(item_ty, len) => match try_const_usize(len) { Some(0) | None => CONTINUE_OPAQUELY_INHABITED, Some(1..) => item_ty.super_visit_with(self, outer_binder), }, @@ -160,14 +160,3 @@ impl UninhabitedFrom<'_> { } } } - -fn try_usize_const(c: &Const) -> Option<u128> { - let data = &c.data(Interner); - if data.ty.kind(Interner) != &TyKind::Scalar(chalk_ir::Scalar::Uint(chalk_ir::UintTy::Usize)) { - return None; - } - match data.value { - ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(value) }) => Some(value), - _ => None, - } -} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs index 7bf73560c..aea7e9762 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/interner.rs @@ -1,10 +1,10 @@ //! Implementation of the Chalk `Interner` trait, which allows customizing the //! representation of the various objects Chalk deals with (types, goals etc.). -use crate::{chalk_db, tls, GenericArg}; +use crate::{chalk_db, tls, ConstScalar, GenericArg}; use base_db::salsa::InternId; use chalk_ir::{Goal, GoalData}; -use hir_def::{type_ref::ConstScalar, TypeAliasId}; +use hir_def::TypeAliasId; use intern::{impl_internable, Interned}; use smallvec::SmallVec; use std::{fmt, sync::Arc}; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs index f21b4f84c..b95bb01fc 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout.rs @@ -11,7 +11,7 @@ use hir_def::{ }; use stdx::never; -use crate::{db::HirDatabase, Interner, Substitution, Ty}; +use crate::{consteval::try_const_usize, db::HirDatabase, Interner, Substitution, Ty}; use self::adt::struct_variant_idx; pub use self::{ @@ -122,17 +122,9 @@ pub fn layout_of_ty(db: &dyn HirDatabase, ty: &Ty, krate: CrateId) -> Result<Lay cx.univariant(dl, &fields, &ReprOptions::default(), kind).ok_or(LayoutError::Unknown)? } TyKind::Array(element, count) => { - let count = match count.data(Interner).value { - chalk_ir::ConstValue::Concrete(c) => match c.interned { - hir_def::type_ref::ConstScalar::Int(x) => x as u64, - hir_def::type_ref::ConstScalar::UInt(x) => x as u64, - hir_def::type_ref::ConstScalar::Unknown => { - user_error!("unknown const generic parameter") - } - _ => user_error!("mismatched type of const generic parameter"), - }, - _ => return Err(LayoutError::HasPlaceholder), - }; + let count = try_const_usize(&count).ok_or(LayoutError::UserError( + "mismatched type of const generic parameter".to_string(), + ))? as u64; let element = layout_of_ty(db, element, krate)?; let size = element.size.checked_mul(count, dl).ok_or(LayoutError::SizeOverflow)?; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs index cb7968c14..b22d0fe8d 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/adt.rs @@ -76,17 +76,8 @@ pub fn layout_of_adt_query( |min, max| Integer::repr_discr(&dl, &repr, min, max).unwrap_or((Integer::I8, false)), variants.iter_enumerated().filter_map(|(id, _)| { let AdtId::EnumId(e) = def else { return None }; - let d = match db - .const_eval_variant(EnumVariantId { parent: e, local_id: id.0 }) - .ok()? - { - crate::consteval::ComputedExpr::Literal(l) => match l { - hir_def::expr::Literal::Int(i, _) => i, - hir_def::expr::Literal::Uint(i, _) => i as i128, - _ => return None, - }, - _ => return None, - }; + let d = + db.const_eval_discriminant(EnumVariantId { parent: e, local_id: id.0 }).ok()?; Some((id, d)) }), // FIXME: The current code for niche-filling relies on variant indices diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs index 067bdc960..a8971fde3 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/layout/tests.rs @@ -65,25 +65,17 @@ 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()) } #[track_caller] fn check_size_and_align(ra_fixture: &str, minicore: &str, size: u64, align: u64) { let l = eval_goal(ra_fixture, minicore).unwrap(); - assert_eq!(l.size.bytes(), size); - assert_eq!(l.align.abi.bytes(), align); + assert_eq!(l.size.bytes(), size, "size mismatch"); + assert_eq!(l.align.abi.bytes(), align, "align mismatch"); } #[track_caller] @@ -300,4 +292,9 @@ fn enums_with_discriminants() { C, // implicitly becomes 256, so we need two bytes } } + size_and_align! { + enum Goal { + A = 1, // This one is (perhaps surprisingly) zero sized. + } + } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs index 59a5ef8c1..9c63d67ab 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lib.rs @@ -13,6 +13,7 @@ mod builder; mod chalk_db; mod chalk_ext; pub mod consteval; +pub mod mir; mod infer; mod inhabitedness; mod interner; @@ -34,7 +35,7 @@ mod tests; #[cfg(test)] mod test_db; -use std::sync::Arc; +use std::{collections::HashMap, hash::Hash, sync::Arc}; use chalk_ir::{ fold::{Shift, TypeFoldable}, @@ -42,10 +43,11 @@ use chalk_ir::{ visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor}, NoSolution, TyData, }; +use either::Either; use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId}; use hir_expand::name; -use itertools::Either; use la_arena::{Arena, Idx}; +use mir::MirEvalError; use rustc_hash::FxHashSet; use traits::FnTrait; use utils::Generics; @@ -145,6 +147,49 @@ pub type ConstrainedSubst = chalk_ir::ConstrainedSubst<Interner>; pub type Guidance = chalk_solve::Guidance<Interner>; pub type WhereClause = chalk_ir::WhereClause<Interner>; +/// A constant can have reference to other things. Memory map job is holding +/// the neccessary bits of memory of the const eval session to keep the constant +/// meaningful. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct MemoryMap(pub HashMap<usize, Vec<u8>>); + +impl MemoryMap { + fn insert(&mut self, addr: usize, x: Vec<u8>) { + self.0.insert(addr, x); + } + + /// This functions convert each address by a function `f` which gets the byte intervals and assign an address + /// to them. It is useful when you want to load a constant with a memory map in a new memory. You can pass an + /// allocator function as `f` and it will return a mapping of old addresses to new addresses. + fn transform_addresses( + &self, + mut f: impl FnMut(&[u8]) -> Result<usize, MirEvalError>, + ) -> Result<HashMap<usize, usize>, MirEvalError> { + self.0.iter().map(|x| Ok((*x.0, f(x.1)?))).collect() + } +} + +/// A concrete constant value +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConstScalar { + Bytes(Vec<u8>, MemoryMap), + /// Case of an unknown value that rustc might know but we don't + // FIXME: this is a hack to get around chalk not being able to represent unevaluatable + // constants + // https://github.com/rust-lang/rust-analyzer/pull/8813#issuecomment-840679177 + // https://rust-lang.zulipchat.com/#narrow/stream/144729-wg-traits/topic/Handling.20non.20evaluatable.20constants'.20equality/near/238386348 + Unknown, +} + +impl Hash for ConstScalar { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + if let ConstScalar::Bytes(b, _) = self { + b.hash(state) + } + } +} + /// Return an index of a parameter in the generic type parameter list by it's id. pub fn param_idx(db: &dyn HirDatabase, id: TypeOrConstParamId) -> Option<usize> { generics(db.upcast(), id.parent).param_idx(id) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs index 299646737..23b15087e 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/lower.rs @@ -16,6 +16,7 @@ use chalk_ir::{ cast::Cast, fold::Shift, fold::TypeFoldable, interner::HasInterner, Mutability, Safety, }; +use either::Either; use hir_def::{ adt::StructKind, body::{Expander, LowerCtx}, @@ -26,16 +27,13 @@ use hir_def::{ lang_item::{lang_attr, LangItem}, path::{GenericArg, ModPath, Path, PathKind, PathSegment, PathSegments}, resolver::{HasResolver, Resolver, TypeNs}, - type_ref::{ - ConstScalarOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef, - }, + type_ref::{ConstRefOrPath, TraitBoundModifier, TraitRef as HirTraitRef, TypeBound, TypeRef}, AdtId, AssocItemId, ConstId, ConstParamId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LocalFieldId, Lookup, ModuleDefId, StaticId, StructId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, VariantId, }; use hir_expand::{name::Name, ExpandResult}; use intern::Interned; -use itertools::Either; use la_arena::{Arena, ArenaMap}; use rustc_hash::FxHashSet; use smallvec::SmallVec; @@ -44,7 +42,7 @@ use syntax::ast; use crate::{ all_super_traits, - consteval::{intern_const_scalar, path_to_const, unknown_const, unknown_const_as_generic}, + consteval::{intern_const_ref, path_to_const, unknown_const, unknown_const_as_generic}, db::HirDatabase, make_binders, mapping::{from_chalk_trait_id, ToChalk}, @@ -524,6 +522,10 @@ impl<'a> TyLoweringContext<'a> { }; return (ty, None); } + TypeNs::TraitAliasId(_) => { + // FIXME(trait_alias): Implement trait alias. + return (TyKind::Error.intern(Interner), None); + } TypeNs::GenericParam(param_id) => { let generics = generics( self.db.upcast(), @@ -879,6 +881,7 @@ impl<'a> TyLoweringContext<'a> { ) -> Option<TraitRef> { let resolved = match self.resolver.resolve_path_in_type_ns_fully(self.db.upcast(), path.mod_path())? { + // FIXME(trait_alias): We need to handle trait alias here. TypeNs::TraitId(tr) => tr, _ => return None, }; @@ -968,7 +971,7 @@ impl<'a> TyLoweringContext<'a> { // - `Destruct` impls are built-in in 1.62 (current nightlies as of 08-04-2022), so until // the builtin impls are supported by Chalk, we ignore them here. if let Some(lang) = lang_attr(self.db.upcast(), tr.hir_trait_id()) { - if lang == "drop" || lang == "destruct" { + if matches!(lang, LangItem::Drop | LangItem::Destruct) { return false; } } @@ -1444,6 +1447,7 @@ pub(crate) fn trait_environment_query( GenericDefId::FunctionId(f) => Some(f.lookup(db.upcast()).container), GenericDefId::AdtId(_) => None, GenericDefId::TraitId(_) => None, + GenericDefId::TraitAliasId(_) => None, GenericDefId::TypeAliasId(t) => Some(t.lookup(db.upcast()).container), GenericDefId::ImplId(_) => None, GenericDefId::EnumVariantId(_) => None, @@ -1583,10 +1587,10 @@ pub(crate) fn generic_defaults_recover( .iter_id() .map(|id| { let val = match id { - itertools::Either::Left(_) => { + Either::Left(_) => { GenericArgData::Ty(TyKind::Error.intern(Interner)).intern(Interner) } - itertools::Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), + Either::Right(id) => unknown_const_as_generic(db.const_param_ty(id)), }; crate::make_binders(db, &generic_params, val) }) @@ -1919,7 +1923,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>( arg: &'a GenericArg, this: &mut T, for_type: impl FnOnce(&mut T, &TypeRef) -> Ty + 'a, - for_const: impl FnOnce(&mut T, &ConstScalarOrPath, Ty) -> Const + 'a, + for_const: impl FnOnce(&mut T, &ConstRefOrPath, Ty) -> Const + 'a, ) -> Option<crate::GenericArg> { let kind = match kind_id { Either::Left(_) => ParamKind::Type, @@ -1947,7 +1951,7 @@ pub(crate) fn generic_arg_to_chalk<'a, T>( let p = p.mod_path(); if p.kind == PathKind::Plain { if let [n] = p.segments() { - let c = ConstScalarOrPath::Path(n.clone()); + let c = ConstRefOrPath::Path(n.clone()); return Some( GenericArgData::Const(for_const(this, &c, c_ty)).intern(Interner), ); @@ -1964,14 +1968,14 @@ pub(crate) fn const_or_path_to_chalk( db: &dyn HirDatabase, resolver: &Resolver, expected_ty: Ty, - value: &ConstScalarOrPath, + value: &ConstRefOrPath, mode: ParamLoweringMode, args: impl FnOnce() -> Generics, debruijn: DebruijnIndex, ) -> Const { match value { - ConstScalarOrPath::Scalar(s) => intern_const_scalar(*s, expected_ty), - ConstScalarOrPath::Path(n) => { + ConstRefOrPath::Scalar(s) => intern_const_ref(db, s, expected_ty, resolver.krate()), + ConstRefOrPath::Path(n) => { let path = ModPath::from_segments(PathKind::Plain, Some(n.clone())); path_to_const(db, resolver, &path, mode, args, debruijn) .unwrap_or_else(|| unknown_const(expected_ty)) diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs index 8c7714b9a..f3a27632b 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/method_resolution.rs @@ -19,13 +19,13 @@ use stdx::never; use crate::{ autoderef::{self, AutoderefKind}, db::HirDatabase, - from_foreign_def_id, + from_chalk_trait_id, from_foreign_def_id, infer::{unify::InferenceTable, Adjust, Adjustment, AutoBorrow, OverloadedDeref, PointerCast}, primitive::{FloatTy, IntTy, UintTy}, static_lifetime, to_chalk_trait_id, utils::all_super_traits, - AdtId, Canonical, CanonicalVarKinds, DebruijnIndex, ForeignDefId, InEnvironment, Interner, - Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, + AdtId, Canonical, CanonicalVarKinds, DebruijnIndex, DynTyExt, ForeignDefId, InEnvironment, + Interner, Scalar, Substitution, TraitEnvironment, TraitRef, TraitRefExt, Ty, TyBuilder, TyExt, }; /// This is used as a key for indexing impls. @@ -266,11 +266,12 @@ impl TraitImpls { #[derive(Debug, Eq, PartialEq)] pub struct InherentImpls { map: FxHashMap<TyFingerprint, Vec<ImplId>>, + invalid_impls: Vec<ImplId>, } impl InherentImpls { pub(crate) fn inherent_impls_in_crate_query(db: &dyn HirDatabase, krate: CrateId) -> Arc<Self> { - let mut impls = Self { map: FxHashMap::default() }; + let mut impls = Self { map: FxHashMap::default(), invalid_impls: Vec::default() }; let crate_def_map = db.crate_def_map(krate); impls.collect_def_map(db, &crate_def_map); @@ -283,7 +284,7 @@ impl InherentImpls { db: &dyn HirDatabase, block: BlockId, ) -> Option<Arc<Self>> { - let mut impls = Self { map: FxHashMap::default() }; + let mut impls = Self { map: FxHashMap::default(), invalid_impls: Vec::default() }; if let Some(block_def_map) = db.block_def_map(block) { impls.collect_def_map(db, &block_def_map); impls.shrink_to_fit(); @@ -306,11 +307,17 @@ impl InherentImpls { } let self_ty = db.impl_self_ty(impl_id); - let fp = TyFingerprint::for_inherent_impl(self_ty.skip_binders()); - if let Some(fp) = fp { - self.map.entry(fp).or_default().push(impl_id); + let self_ty = self_ty.skip_binders(); + + match is_inherent_impl_coherent(db, def_map, &data, self_ty) { + true => { + // `fp` should only be `None` in error cases (either erroneous code or incomplete name resolution) + if let Some(fp) = TyFingerprint::for_inherent_impl(self_ty) { + self.map.entry(fp).or_default().push(impl_id); + } + } + false => self.invalid_impls.push(impl_id), } - // `fp` should only be `None` in error cases (either erroneous code or incomplete name resolution) } // To better support custom derives, collect impls in all unnamed const items. @@ -334,6 +341,10 @@ impl InherentImpls { pub fn all_impls(&self) -> impl Iterator<Item = ImplId> + '_ { self.map.values().flat_map(|v| v.iter().copied()) } + + pub fn invalid_impls(&self) -> &[ImplId] { + &self.invalid_impls + } } pub(crate) fn incoherent_inherent_impl_crates( @@ -579,8 +590,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, @@ -660,10 +671,10 @@ pub fn lookup_impl_const( env: Arc<TraitEnvironment>, const_id: ConstId, subs: Substitution, -) -> ConstId { +) -> (ConstId, Substitution) { let trait_id = match const_id.lookup(db.upcast()).container { ItemContainerId::TraitId(id) => id, - _ => return const_id, + _ => return (const_id, subs), }; let substitution = Substitution::from_iter(Interner, subs.iter(Interner)); let trait_ref = TraitRef { trait_id: to_chalk_trait_id(trait_id), substitution }; @@ -671,12 +682,14 @@ pub fn lookup_impl_const( let const_data = db.const_data(const_id); let name = match const_data.name.as_ref() { Some(name) => name, - None => return const_id, + None => return (const_id, subs), }; lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name) - .and_then(|assoc| if let AssocItemId::ConstId(id) = assoc { Some(id) } else { None }) - .unwrap_or(const_id) + .and_then( + |assoc| if let (AssocItemId::ConstId(id), s) = assoc { Some((id, s)) } else { None }, + ) + .unwrap_or((const_id, subs)) } /// Looks up the impl method that actually runs for the trait method `func`. @@ -687,10 +700,10 @@ pub fn lookup_impl_method( env: Arc<TraitEnvironment>, func: FunctionId, fn_subst: Substitution, -) -> FunctionId { +) -> (FunctionId, Substitution) { let trait_id = match func.lookup(db.upcast()).container { ItemContainerId::TraitId(id) => id, - _ => return func, + _ => return (func, fn_subst), }; let trait_params = db.generic_params(trait_id.into()).type_or_consts.len(); let fn_params = fn_subst.len(Interner) - trait_params; @@ -701,8 +714,14 @@ pub fn lookup_impl_method( let name = &db.function_data(func).name; lookup_impl_assoc_item_for_trait_ref(trait_ref, db, env, name) - .and_then(|assoc| if let AssocItemId::FunctionId(id) = assoc { Some(id) } else { None }) - .unwrap_or(func) + .and_then(|assoc| { + if let (AssocItemId::FunctionId(id), subst) = assoc { + Some((id, subst)) + } else { + None + } + }) + .unwrap_or((func, fn_subst)) } fn lookup_impl_assoc_item_for_trait_ref( @@ -710,7 +729,7 @@ fn lookup_impl_assoc_item_for_trait_ref( db: &dyn HirDatabase, env: Arc<TraitEnvironment>, name: &Name, -) -> Option<AssocItemId> { +) -> Option<(AssocItemId, Substitution)> { let self_ty = trait_ref.self_type_parameter(Interner); let self_ty_fp = TyFingerprint::for_trait_impl(&self_ty)?; let impls = db.trait_impls_in_deps(env.krate); @@ -718,8 +737,8 @@ fn lookup_impl_assoc_item_for_trait_ref( let table = InferenceTable::new(db, env); - let impl_data = find_matching_impl(impls, table, trait_ref)?; - impl_data.items.iter().find_map(|&it| match it { + let (impl_data, impl_subst) = find_matching_impl(impls, table, trait_ref)?; + let item = impl_data.items.iter().find_map(|&it| match it { AssocItemId::FunctionId(f) => { (db.function_data(f).name == *name).then_some(AssocItemId::FunctionId(f)) } @@ -730,14 +749,15 @@ fn lookup_impl_assoc_item_for_trait_ref( .map(|n| n == name) .and_then(|result| if result { Some(AssocItemId::ConstId(c)) } else { None }), AssocItemId::TypeAliasId(_) => None, - }) + })?; + Some((item, impl_subst)) } fn find_matching_impl( mut impls: impl Iterator<Item = ImplId>, mut table: InferenceTable<'_>, actual_trait_ref: TraitRef, -) -> Option<Arc<ImplData>> { +) -> Option<(Arc<ImplData>, Substitution)> { let db = table.db; loop { let impl_ = impls.next()?; @@ -758,7 +778,7 @@ fn find_matching_impl( .into_iter() .map(|b| b.cast(Interner)); let goal = crate::Goal::all(Interner, wcs); - table.try_obligation(goal).map(|_| impl_data) + table.try_obligation(goal).map(|_| (impl_data, table.resolve_completely(impl_substs))) }); if r.is_some() { break r; @@ -766,6 +786,69 @@ fn find_matching_impl( } } +fn is_inherent_impl_coherent( + db: &dyn HirDatabase, + def_map: &DefMap, + impl_data: &ImplData, + self_ty: &Ty, +) -> bool { + let self_ty = self_ty.kind(Interner); + let impl_allowed = match self_ty { + TyKind::Tuple(_, _) + | TyKind::FnDef(_, _) + | TyKind::Array(_, _) + | TyKind::Never + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::Slice(_) + | TyKind::Str + | TyKind::Scalar(_) => def_map.is_rustc_coherence_is_core(), + + &TyKind::Adt(AdtId(adt), _) => adt.module(db.upcast()).krate() == def_map.krate(), + TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| { + from_chalk_trait_id(trait_ref.trait_id).module(db.upcast()).krate() == def_map.krate() + }), + + _ => true, + }; + impl_allowed || { + let rustc_has_incoherent_inherent_impls = match self_ty { + TyKind::Tuple(_, _) + | TyKind::FnDef(_, _) + | TyKind::Array(_, _) + | TyKind::Never + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::Slice(_) + | TyKind::Str + | TyKind::Scalar(_) => true, + + &TyKind::Adt(AdtId(adt), _) => match adt { + hir_def::AdtId::StructId(it) => { + db.struct_data(it).rustc_has_incoherent_inherent_impls + } + hir_def::AdtId::UnionId(it) => { + db.union_data(it).rustc_has_incoherent_inherent_impls + } + hir_def::AdtId::EnumId(it) => db.enum_data(it).rustc_has_incoherent_inherent_impls, + }, + TyKind::Dyn(it) => it.principal().map_or(false, |trait_ref| { + db.trait_data(from_chalk_trait_id(trait_ref.trait_id)) + .rustc_has_incoherent_inherent_impls + }), + + _ => false, + }; + rustc_has_incoherent_inherent_impls + && !impl_data.items.is_empty() + && impl_data.items.iter().copied().all(|assoc| match assoc { + AssocItemId::FunctionId(it) => db.function_data(it).rustc_allow_incoherent_impl, + AssocItemId::ConstId(it) => db.const_data(it).rustc_allow_incoherent_impl, + AssocItemId::TypeAliasId(it) => db.type_alias_data(it).rustc_allow_incoherent_impl, + }) + } +} + pub fn iterate_path_candidates( ty: &Canonical<Ty>, db: &dyn HirDatabase, @@ -821,9 +904,9 @@ pub fn iterate_method_candidates_dyn( let mut table = InferenceTable::new(db, env.clone()); let ty = table.instantiate_canonical(ty.clone()); - let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); + let deref_chain = autoderef_method_receiver(&mut table, ty); - let result = deref_chain.into_iter().zip(adj).try_for_each(|(receiver_ty, adj)| { + let result = deref_chain.into_iter().try_for_each(|(receiver_ty, adj)| { iterate_method_candidates_with_autoref( &receiver_ty, adj, @@ -867,16 +950,20 @@ fn iterate_method_candidates_with_autoref( return ControlFlow::Continue(()); } - iterate_method_candidates_by_receiver( - receiver_ty, - first_adjustment.clone(), - db, - env.clone(), - traits_in_scope, - visible_from_module, - name, - &mut callback, - )?; + let mut iterate_method_candidates_by_receiver = move |receiver_ty, first_adjustment| { + iterate_method_candidates_by_receiver( + receiver_ty, + first_adjustment, + db, + env.clone(), + traits_in_scope, + visible_from_module, + name, + &mut callback, + ) + }; + + iterate_method_candidates_by_receiver(receiver_ty, first_adjustment.clone())?; let refed = Canonical { value: TyKind::Ref(Mutability::Not, static_lifetime(), receiver_ty.value.clone()) @@ -884,16 +971,7 @@ fn iterate_method_candidates_with_autoref( binders: receiver_ty.binders.clone(), }; - iterate_method_candidates_by_receiver( - &refed, - first_adjustment.with_autoref(Mutability::Not), - db, - env.clone(), - traits_in_scope, - visible_from_module, - name, - &mut callback, - )?; + iterate_method_candidates_by_receiver(&refed, first_adjustment.with_autoref(Mutability::Not))?; let ref_muted = Canonical { value: TyKind::Ref(Mutability::Mut, static_lifetime(), receiver_ty.value.clone()) @@ -904,12 +982,6 @@ fn iterate_method_candidates_with_autoref( iterate_method_candidates_by_receiver( &ref_muted, first_adjustment.with_autoref(Mutability::Mut), - db, - env, - traits_in_scope, - visible_from_module, - name, - &mut callback, ) } @@ -1210,8 +1282,8 @@ pub fn resolve_indexing_op( ) -> Option<ReceiverAdjustments> { let mut table = InferenceTable::new(db, env.clone()); let ty = table.instantiate_canonical(ty); - let (deref_chain, adj) = autoderef_method_receiver(&mut table, ty); - for (ty, adj) in deref_chain.into_iter().zip(adj) { + let deref_chain = autoderef_method_receiver(&mut table, ty); + for (ty, adj) in deref_chain { let goal = generic_implements_goal(db, env.clone(), index_trait, &ty); if db.trait_solve(env.krate, goal.cast(Interner)).is_some() { return Some(adj); @@ -1421,25 +1493,24 @@ fn generic_implements_goal( fn autoderef_method_receiver( table: &mut InferenceTable<'_>, ty: Ty, -) -> (Vec<Canonical<Ty>>, Vec<ReceiverAdjustments>) { - let (mut deref_chain, mut adjustments): (Vec<_>, Vec<_>) = (Vec::new(), Vec::new()); +) -> Vec<(Canonical<Ty>, ReceiverAdjustments)> { + let mut deref_chain: Vec<_> = Vec::new(); let mut autoderef = autoderef::Autoderef::new(table, ty); while let Some((ty, derefs)) = autoderef.next() { - deref_chain.push(autoderef.table.canonicalize(ty).value); - adjustments.push(ReceiverAdjustments { - autoref: None, - autoderefs: derefs, - unsize_array: false, - }); + deref_chain.push(( + autoderef.table.canonicalize(ty).value, + ReceiverAdjustments { autoref: None, autoderefs: derefs, unsize_array: false }, + )); } // As a last step, we can do array unsizing (that's the only unsizing that rustc does for method receivers!) - if let (Some((TyKind::Array(parameters, _), binders)), Some(adj)) = ( - deref_chain.last().map(|ty| (ty.value.kind(Interner), ty.binders.clone())), - adjustments.last().cloned(), - ) { + if let Some((TyKind::Array(parameters, _), binders, adj)) = + deref_chain.last().map(|(ty, adj)| (ty.value.kind(Interner), ty.binders.clone(), adj)) + { let unsized_ty = TyKind::Slice(parameters.clone()).intern(Interner); - deref_chain.push(Canonical { value: unsized_ty, binders }); - adjustments.push(ReceiverAdjustments { unsize_array: true, ..adj }); + deref_chain.push(( + Canonical { value: unsized_ty, binders }, + ReceiverAdjustments { unsize_array: true, ..adj.clone() }, + )); } - (deref_chain, adjustments) + deref_chain } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs new file mode 100644 index 000000000..7c1cbbdf5 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir.rs @@ -0,0 +1,863 @@ +//! MIR definitions and implementation + +use std::{fmt::Display, iter}; + +use crate::{ + infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty, +}; +use chalk_ir::Mutability; +use hir_def::{ + expr::{BindingId, Expr, ExprId, Ordering, PatId}, + DefWithBodyId, FieldId, UnionId, VariantId, +}; +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}; + +pub type BasicBlockId = Idx<BasicBlock>; +pub type LocalId = Idx<Local>; + +fn return_slot() -> LocalId { + LocalId::from_raw(RawIdx::from(0)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Local { + pub ty: Ty, +} + +/// An operand in MIR represents a "value" in Rust, the definition of which is undecided and part of +/// the memory model. One proposal for a definition of values can be found [on UCG][value-def]. +/// +/// [value-def]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/value-domain.md +/// +/// The most common way to create values is via loading a place. Loading a place is an operation +/// which reads the memory of the place and converts it to a value. This is a fundamentally *typed* +/// operation. The nature of the value produced depends on the type of the conversion. Furthermore, +/// there may be other effects: if the type has a validity constraint loading the place might be UB +/// if the validity constraint is not met. +/// +/// **Needs clarification:** Ralf proposes that loading a place not have side-effects. +/// This is what is implemented in miri today. Are these the semantics we want for MIR? Is this +/// something we can even decide without knowing more about Rust's memory model? +/// +/// **Needs clarifiation:** Is loading a place that has its variant index set well-formed? Miri +/// currently implements it, but it seems like this may be something to check against in the +/// validator. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Operand { + /// Creates a value by loading the given place. + /// + /// Before drop elaboration, the type of the place must be `Copy`. After drop elaboration there + /// is no such requirement. + Copy(Place), + + /// Creates a value by performing loading the place, just like the `Copy` operand. + /// + /// This *may* additionally overwrite the place with `uninit` bytes, depending on how we decide + /// in [UCG#188]. You should not emit MIR that may attempt a subsequent second load of this + /// place without first re-initializing it. + /// + /// [UCG#188]: https://github.com/rust-lang/unsafe-code-guidelines/issues/188 + Move(Place), + /// Constants are already semantically values, and remain unchanged. + Constant(Const), +} + +impl Operand { + fn from_concrete_const(data: Vec<u8>, memory_map: MemoryMap, ty: Ty) -> Self { + Operand::Constant(intern_const_scalar(ConstScalar::Bytes(data, memory_map), ty)) + } + + 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)] +pub enum ProjectionElem<V, T> { + Deref, + Field(FieldId), + TupleField(usize), + Index(V), + ConstantIndex { offset: u64, min_length: u64, from_end: bool }, + Subslice { from: u64, to: u64, from_end: bool }, + //Downcast(Option<Symbol>, VariantIdx), + OpaqueCast(T), +} + +type PlaceElem = ProjectionElem<LocalId, Ty>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Place { + pub local: LocalId, + pub projection: Vec<PlaceElem>, +} + +impl From<LocalId> for Place { + fn from(local: LocalId) -> Self { + Self { local, projection: vec![] } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum AggregateKind { + /// The type is of the element + Array(Ty), + /// The type is of the tuple + Tuple(Ty), + Adt(VariantId, Substitution), + Union(UnionId, FieldId), + //Closure(LocalDefId, SubstsRef), + //Generator(LocalDefId, SubstsRef, Movability), +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct SwitchTargets { + /// Possible values. The locations to branch to in each case + /// are found in the corresponding indices from the `targets` vector. + values: SmallVec<[u128; 1]>, + + /// Possible branch sites. The last element of this vector is used + /// for the otherwise branch, so targets.len() == values.len() + 1 + /// should hold. + // + // This invariant is quite non-obvious and also could be improved. + // One way to make this invariant is to have something like this instead: + // + // branches: Vec<(ConstInt, BasicBlock)>, + // otherwise: Option<BasicBlock> // exhaustive if None + // + // However we’ve decided to keep this as-is until we figure a case + // where some other approach seems to be strictly better than other. + targets: SmallVec<[BasicBlockId; 2]>, +} + +impl SwitchTargets { + /// Creates switch targets from an iterator of values and target blocks. + /// + /// The iterator may be empty, in which case the `SwitchInt` instruction is equivalent to + /// `goto otherwise;`. + pub fn new( + targets: impl Iterator<Item = (u128, BasicBlockId)>, + otherwise: BasicBlockId, + ) -> Self { + let (values, mut targets): (SmallVec<_>, SmallVec<_>) = targets.unzip(); + targets.push(otherwise); + Self { values, targets } + } + + /// Builds a switch targets definition that jumps to `then` if the tested value equals `value`, + /// and to `else_` if not. + pub fn static_if(value: u128, then: BasicBlockId, else_: BasicBlockId) -> Self { + Self { values: smallvec![value], targets: smallvec![then, else_] } + } + + /// Returns the fallback target that is jumped to when none of the values match the operand. + pub fn otherwise(&self) -> BasicBlockId { + *self.targets.last().unwrap() + } + + /// Returns an iterator over the switch targets. + /// + /// The iterator will yield tuples containing the value and corresponding target to jump to, not + /// including the `otherwise` fallback target. + /// + /// Note that this may yield 0 elements. Only the `otherwise` branch is mandatory. + pub fn iter(&self) -> impl Iterator<Item = (u128, BasicBlockId)> + '_ { + 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. + pub fn target_for_value(&self, value: u128) -> BasicBlockId { + self.iter().find_map(|(v, t)| (v == value).then_some(t)).unwrap_or_else(|| self.otherwise()) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Terminator { + /// Block has one successor; we continue execution there. + Goto { target: BasicBlockId }, + + /// Switches based on the computed value. + /// + /// First, evaluates the `discr` operand. The type of the operand must be a signed or unsigned + /// integer, char, or bool, and must match the given type. Then, if the list of switch targets + /// contains the computed value, continues execution at the associated basic block. Otherwise, + /// continues execution at the "otherwise" basic block. + /// + /// Target values may not appear more than once. + SwitchInt { + /// The discriminant value being tested. + discr: Operand, + + targets: SwitchTargets, + }, + + /// Indicates that the landing pad is finished and that the process should continue unwinding. + /// + /// Like a return, this marks the end of this invocation of the function. + /// + /// Only permitted in cleanup blocks. `Resume` is not permitted with `-C unwind=abort` after + /// deaggregation runs. + Resume, + + /// Indicates that the landing pad is finished and that the process should abort. + /// + /// Used to prevent unwinding for foreign items or with `-C unwind=abort`. Only permitted in + /// cleanup blocks. + Abort, + + /// Returns from the function. + /// + /// Like function calls, the exact semantics of returns in Rust are unclear. Returning very + /// likely at least assigns the value currently in the return place (`_0`) to the place + /// specified in the associated `Call` terminator in the calling function, as if assigned via + /// `dest = move _0`. It might additionally do other things, like have side-effects in the + /// aliasing model. + /// + /// If the body is a generator body, this has slightly different semantics; it instead causes a + /// `GeneratorState::Returned(_0)` to be created (as if by an `Aggregate` rvalue) and assigned + /// to the return place. + Return, + + /// Indicates a terminator that can never be reached. + /// + /// Executing this terminator is UB. + Unreachable, + + /// The behavior of this statement differs significantly before and after drop elaboration. + /// After drop elaboration, `Drop` executes the drop glue for the specified place, after which + /// it continues execution/unwinds at the given basic blocks. It is possible that executing drop + /// glue is special - this would be part of Rust's memory model. (**FIXME**: due we have an + /// issue tracking if drop glue has any interesting semantics in addition to those of a function + /// call?) + /// + /// `Drop` before drop elaboration is a *conditional* execution of the drop glue. Specifically, the + /// `Drop` will be executed if... + /// + /// **Needs clarification**: End of that sentence. This in effect should document the exact + /// behavior of drop elaboration. The following sounds vaguely right, but I'm not quite sure: + /// + /// > The drop glue is executed if, among all statements executed within this `Body`, an assignment to + /// > the place or one of its "parents" occurred more recently than a move out of it. This does not + /// > consider indirect assignments. + Drop { place: Place, target: BasicBlockId, unwind: Option<BasicBlockId> }, + + /// Drops the place and assigns a new value to it. + /// + /// This first performs the exact same operation as the pre drop-elaboration `Drop` terminator; + /// it then additionally assigns the `value` to the `place` as if by an assignment statement. + /// This assignment occurs both in the unwind and the regular code paths. The semantics are best + /// explained by the elaboration: + /// + /// ```ignore (MIR) + /// BB0 { + /// DropAndReplace(P <- V, goto BB1, unwind BB2) + /// } + /// ``` + /// + /// becomes + /// + /// ```ignore (MIR) + /// BB0 { + /// Drop(P, goto BB1, unwind BB2) + /// } + /// BB1 { + /// // P is now uninitialized + /// P <- V + /// } + /// BB2 { + /// // P is now uninitialized -- its dtor panicked + /// P <- V + /// } + /// ``` + /// + /// Disallowed after drop elaboration. + DropAndReplace { + place: Place, + value: Operand, + target: BasicBlockId, + unwind: Option<BasicBlockId>, + }, + + /// Roughly speaking, evaluates the `func` operand and the arguments, and starts execution of + /// the referred to function. The operand types must match the argument types of the function. + /// The return place type must match the return type. The type of the `func` operand must be + /// callable, meaning either a function pointer, a function type, or a closure type. + /// + /// **Needs clarification**: The exact semantics of this. Current backends rely on `move` + /// operands not aliasing the return place. It is unclear how this is justified in MIR, see + /// [#71117]. + /// + /// [#71117]: https://github.com/rust-lang/rust/issues/71117 + Call { + /// The function that’s being called. + func: Operand, + /// Arguments the function is called with. + /// These are owned by the callee, which is free to modify them. + /// This allows the memory occupied by "by-value" arguments to be + /// reused across function calls without duplicating the contents. + args: Vec<Operand>, + /// Where the returned value will be written + destination: Place, + /// Where to go after this call returns. If none, the call necessarily diverges. + target: Option<BasicBlockId>, + /// Cleanups to be done if the call unwinds. + cleanup: Option<BasicBlockId>, + /// `true` if this is from a call in HIR rather than from an overloaded + /// operator. True for overloaded function call. + from_hir_call: bool, + // This `Span` is the span of the function, without the dot and receiver + // (e.g. `foo(a, b)` in `x.foo(a, b)` + //fn_span: Span, + }, + + /// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`, + /// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some + /// unspecified constant as the function to call, all the operands stored in the `AssertMessage` + /// as parameters, and `None` for the destination. Keep in mind that the `cleanup` path is not + /// necessarily executed even in the case of a panic, for example in `-C panic=abort`. If the + /// assertion does not fail, execution continues at the specified basic block. + Assert { + cond: Operand, + expected: bool, + //msg: AssertMessage, + target: BasicBlockId, + cleanup: Option<BasicBlockId>, + }, + + /// Marks a suspend point. + /// + /// Like `Return` terminators in generator bodies, this computes `value` and then a + /// `GeneratorState::Yielded(value)` as if by `Aggregate` rvalue. That value is then assigned to + /// the return place of the function calling this one, and execution continues in the calling + /// function. When next invoked with the same first argument, execution of this function + /// continues at the `resume` basic block, with the second argument written to the `resume_arg` + /// place. If the generator is dropped before then, the `drop` basic block is invoked. + /// + /// Not permitted in bodies that are not generator bodies, or after generator lowering. + /// + /// **Needs clarification**: What about the evaluation order of the `resume_arg` and `value`? + Yield { + /// The value to return. + value: Operand, + /// Where to resume to. + resume: BasicBlockId, + /// The place to store the resume argument in. + resume_arg: Place, + /// Cleanup to be done if the generator is dropped at this suspend point. + drop: Option<BasicBlockId>, + }, + + /// Indicates the end of dropping a generator. + /// + /// Semantically just a `return` (from the generators drop glue). Only permitted in the same situations + /// as `yield`. + /// + /// **Needs clarification**: Is that even correct? The generator drop code is always confusing + /// to me, because it's not even really in the current body. + /// + /// **Needs clarification**: Are there type system constraints on these terminators? Should + /// there be a "block type" like `cleanup` blocks for them? + GeneratorDrop, + + /// A block where control flow only ever takes one real path, but borrowck needs to be more + /// conservative. + /// + /// At runtime this is semantically just a goto. + /// + /// Disallowed after drop elaboration. + FalseEdge { + /// The target normal control flow will take. + real_target: BasicBlockId, + /// A block control flow could conceptually jump to, but won't in + /// practice. + imaginary_target: BasicBlockId, + }, + + /// A terminator for blocks that only take one path in reality, but where we reserve the right + /// to unwind in borrowck, even if it won't happen in practice. This can arise in infinite loops + /// with no function calls for example. + /// + /// At runtime this is semantically just a goto. + /// + /// Disallowed after drop elaboration. + FalseUnwind { + /// The target normal control flow will take. + real_target: BasicBlockId, + /// The imaginary cleanup block link. This particular path will never be taken + /// in practice, but in order to avoid fragility we want to always + /// consider it in borrowck. We don't want to accept programs which + /// pass borrowck only when `panic=abort` or some assertions are disabled + /// due to release vs. debug mode builds. This needs to be an `Option` because + /// of the `remove_noop_landing_pads` and `abort_unwinding_calls` passes. + unwind: Option<BasicBlockId>, + }, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BorrowKind { + /// Data must be immutable and is aliasable. + Shared, + + /// The immediately borrowed place must be immutable, but projections from + /// it don't need to be. For example, a shallow borrow of `a.b` doesn't + /// conflict with a mutable borrow of `a.b.c`. + /// + /// This is used when lowering matches: when matching on a place we want to + /// ensure that place have the same value from the start of the match until + /// an arm is selected. This prevents this code from compiling: + /// ```compile_fail,E0510 + /// let mut x = &Some(0); + /// match *x { + /// None => (), + /// Some(_) if { x = &None; false } => (), + /// Some(_) => (), + /// } + /// ``` + /// This can't be a shared borrow because mutably borrowing (*x as Some).0 + /// should not prevent `if let None = x { ... }`, for example, because the + /// mutating `(*x as Some).0` can't affect the discriminant of `x`. + /// We can also report errors with this kind of borrow differently. + Shallow, + + /// Data must be immutable but not aliasable. This kind of borrow + /// cannot currently be expressed by the user and is used only in + /// implicit closure bindings. It is needed when the closure is + /// borrowing or mutating a mutable referent, e.g.: + /// ``` + /// let mut z = 3; + /// let x: &mut isize = &mut z; + /// let y = || *x += 5; + /// ``` + /// If we were to try to translate this closure into a more explicit + /// form, we'd encounter an error with the code as written: + /// ```compile_fail,E0594 + /// struct Env<'a> { x: &'a &'a mut isize } + /// let mut z = 3; + /// let x: &mut isize = &mut z; + /// let y = (&mut Env { x: &x }, fn_ptr); // Closure is pair of env and fn + /// fn fn_ptr(env: &mut Env) { **env.x += 5; } + /// ``` + /// This is then illegal because you cannot mutate an `&mut` found + /// in an aliasable location. To solve, you'd have to translate with + /// an `&mut` borrow: + /// ```compile_fail,E0596 + /// struct Env<'a> { x: &'a mut &'a mut isize } + /// let mut z = 3; + /// let x: &mut isize = &mut z; + /// let y = (&mut Env { x: &mut x }, fn_ptr); // changed from &x to &mut x + /// fn fn_ptr(env: &mut Env) { **env.x += 5; } + /// ``` + /// Now the assignment to `**env.x` is legal, but creating a + /// mutable pointer to `x` is not because `x` is not mutable. We + /// could fix this by declaring `x` as `let mut x`. This is ok in + /// user code, if awkward, but extra weird for closures, since the + /// borrow is hidden. + /// + /// So we introduce a "unique imm" borrow -- the referent is + /// immutable, but not aliasable. This solves the problem. For + /// simplicity, we don't give users the way to express this + /// borrow, it's just used when translating closures. + Unique, + + /// Data is mutable and not aliasable. + Mut { + /// `true` if this borrow arose from method-call auto-ref + /// (i.e., `adjustment::Adjust::Borrow`). + allow_two_phase_borrow: bool, + }, +} + +impl BorrowKind { + fn from_hir(m: hir_def::type_ref::Mutability) -> Self { + match m { + hir_def::type_ref::Mutability::Shared => BorrowKind::Shared, + hir_def::type_ref::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false }, + } + } + + fn from_chalk(m: Mutability) -> Self { + match m { + Mutability::Not => BorrowKind::Shared, + Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false }, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum UnOp { + /// The `!` operator for logical inversion + Not, + /// The `-` operator for negation + Neg, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum BinOp { + /// The `+` operator (addition) + Add, + /// The `-` operator (subtraction) + Sub, + /// The `*` operator (multiplication) + Mul, + /// The `/` operator (division) + /// + /// Division by zero is UB, because the compiler should have inserted checks + /// prior to this. + Div, + /// The `%` operator (modulus) + /// + /// Using zero as the modulus (second operand) is UB, because the compiler + /// should have inserted checks prior to this. + Rem, + /// The `^` operator (bitwise xor) + BitXor, + /// The `&` operator (bitwise and) + BitAnd, + /// The `|` operator (bitwise or) + BitOr, + /// The `<<` operator (shift left) + /// + /// The offset is truncated to the size of the first operand before shifting. + Shl, + /// The `>>` operator (shift right) + /// + /// The offset is truncated to the size of the first operand before shifting. + Shr, + /// The `==` operator (equality) + Eq, + /// The `<` operator (less than) + Lt, + /// The `<=` operator (less than or equal to) + Le, + /// The `!=` operator (not equal to) + Ne, + /// The `>=` operator (greater than or equal to) + Ge, + /// The `>` operator (greater than) + Gt, + /// The `ptr.offset` operator + 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 { + hir_def::expr::ArithOp::Add => BinOp::Add, + hir_def::expr::ArithOp::Mul => BinOp::Mul, + hir_def::expr::ArithOp::Sub => BinOp::Sub, + hir_def::expr::ArithOp::Div => BinOp::Div, + hir_def::expr::ArithOp::Rem => BinOp::Rem, + hir_def::expr::ArithOp::Shl => BinOp::Shl, + hir_def::expr::ArithOp::Shr => BinOp::Shr, + hir_def::expr::ArithOp::BitXor => BinOp::BitXor, + hir_def::expr::ArithOp::BitOr => BinOp::BitOr, + hir_def::expr::ArithOp::BitAnd => BinOp::BitAnd, + } + } +} + +impl From<hir_def::expr::CmpOp> for BinOp { + fn from(value: hir_def::expr::CmpOp) -> Self { + match value { + hir_def::expr::CmpOp::Eq { negated: false } => BinOp::Eq, + hir_def::expr::CmpOp::Eq { negated: true } => BinOp::Ne, + hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: false } => BinOp::Ge, + hir_def::expr::CmpOp::Ord { ordering: Ordering::Greater, strict: true } => BinOp::Gt, + hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: false } => BinOp::Le, + hir_def::expr::CmpOp::Ord { ordering: Ordering::Less, strict: true } => BinOp::Lt, + } + } +} + +impl From<Operand> for Rvalue { + fn from(x: Operand) -> Self { + Self::Use(x) + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum CastKind { + /// An exposing pointer to address cast. A cast between a pointer and an integer type, or + /// between a function pointer and an integer type. + /// See the docs on `expose_addr` for more details. + PointerExposeAddress, + /// An address-to-pointer cast that picks up an exposed provenance. + /// See the docs on `from_exposed_addr` for more details. + PointerFromExposedAddress, + /// All sorts of pointer-to-pointer casts. Note that reference-to-raw-ptr casts are + /// translated into `&raw mut/const *r`, i.e., they are not actually casts. + Pointer(PointerCast), + /// Cast into a dyn* object. + DynStar, + IntToInt, + FloatToInt, + FloatToFloat, + IntToFloat, + PtrToPtr, + FnPtrToPtr, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Rvalue { + /// Yields the operand unchanged + Use(Operand), + + /// Creates an array where each element is the value of the operand. + /// + /// This is the cause of a bug in the case where the repetition count is zero because the value + /// is not dropped, see [#74836]. + /// + /// Corresponds to source code like `[x; 32]`. + /// + /// [#74836]: https://github.com/rust-lang/rust/issues/74836 + //Repeat(Operand, ty::Const), + + /// Creates a reference of the indicated kind to the place. + /// + /// There is not much to document here, because besides the obvious parts the semantics of this + /// are essentially entirely a part of the aliasing model. There are many UCG issues discussing + /// exactly what the behavior of this operation should be. + /// + /// `Shallow` borrows are disallowed after drop lowering. + Ref(BorrowKind, Place), + + /// Creates a pointer/reference to the given thread local. + /// + /// The yielded type is a `*mut T` if the static is mutable, otherwise if the static is extern a + /// `*const T`, and if neither of those apply a `&T`. + /// + /// **Note:** This is a runtime operation that actually executes code and is in this sense more + /// like a function call. Also, eliminating dead stores of this rvalue causes `fn main() {}` to + /// SIGILL for some reason that I (JakobDegen) never got a chance to look into. + /// + /// **Needs clarification**: Are there weird additional semantics here related to the runtime + /// nature of this operation? + //ThreadLocalRef(DefId), + + /// Creates a pointer with the indicated mutability to the place. + /// + /// This is generated by pointer casts like `&v as *const _` or raw address of expressions like + /// `&raw v` or `addr_of!(v)`. + /// + /// Like with references, the semantics of this operation are heavily dependent on the aliasing + /// model. + //AddressOf(Mutability, Place), + + /// Yields the length of the place, as a `usize`. + /// + /// If the type of the place is an array, this is the array length. For slices (`[T]`, not + /// `&[T]`) this accesses the place's metadata to determine the length. This rvalue is + /// ill-formed for places of other types. + Len(Place), + + /// Performs essentially all of the casts that can be performed via `as`. + /// + /// This allows for casts from/to a variety of types. + /// + /// **FIXME**: Document exactly which `CastKind`s allow which types of casts. Figure out why + /// `ArrayToPointer` and `MutToConstPointer` are special. + Cast(CastKind, Operand, Ty), + + // FIXME link to `pointer::offset` when it hits stable. + /// * `Offset` has the same semantics as `pointer::offset`, except that the second + /// parameter may be a `usize` as well. + /// * The comparison operations accept `bool`s, `char`s, signed or unsigned integers, floats, + /// raw pointers, or function pointers and return a `bool`. The types of the operands must be + /// matching, up to the usual caveat of the lifetimes in function pointers. + /// * Left and right shift operations accept signed or unsigned integers not necessarily of the + /// same type and return a value of the same type as their LHS. Like in Rust, the RHS is + /// truncated as needed. + /// * The `Bit*` operations accept signed integers, unsigned integers, or bools with matching + /// types and return a value of that type. + /// * The remaining operations accept signed integers, unsigned integers, or floats with + /// matching types and return a value of that type. + //BinaryOp(BinOp, Box<(Operand, Operand)>), + + /// Same as `BinaryOp`, but yields `(T, bool)` with a `bool` indicating an error condition. + /// + /// When overflow checking is disabled and we are generating run-time code, the error condition + /// is false. Otherwise, and always during CTFE, the error condition is determined as described + /// below. + /// + /// For addition, subtraction, and multiplication on integers the error condition is set when + /// the infinite precision result would be unequal to the actual result. + /// + /// For shift operations on integers the error condition is set when the value of right-hand + /// side is greater than or equal to the number of bits in the type of the left-hand side, or + /// when the value of right-hand side is negative. + /// + /// Other combinations of types and operators are unsupported. + CheckedBinaryOp(BinOp, Operand, Operand), + + /// Computes a value as described by the operation. + //NullaryOp(NullOp, Ty), + + /// Exactly like `BinaryOp`, but less operands. + /// + /// Also does two's-complement arithmetic. Negation requires a signed integer or a float; + /// bitwise not requires a signed integer, unsigned integer, or bool. Both operation kinds + /// return a value with the same type as their operand. + UnaryOp(UnOp, Operand), + + /// Computes the discriminant of the place, returning it as an integer of type + /// [`discriminant_ty`]. Returns zero for types without discriminant. + /// + /// The validity requirements for the underlying value are undecided for this rvalue, see + /// [#91095]. Note too that the value of the discriminant is not the same thing as the + /// variant index; use [`discriminant_for_variant`] to convert. + /// + /// [`discriminant_ty`]: crate::ty::Ty::discriminant_ty + /// [#91095]: https://github.com/rust-lang/rust/issues/91095 + /// [`discriminant_for_variant`]: crate::ty::Ty::discriminant_for_variant + Discriminant(Place), + + /// Creates an aggregate value, like a tuple or struct. + /// + /// This is needed because dataflow analysis needs to distinguish + /// `dest = Foo { x: ..., y: ... }` from `dest.x = ...; dest.y = ...;` in the case that `Foo` + /// has a destructor. + /// + /// Disallowed after deaggregation for all aggregate kinds except `Array` and `Generator`. After + /// generator lowering, `Generator` aggregate kinds are disallowed too. + Aggregate(AggregateKind, Vec<Operand>), + + /// Transmutes a `*mut u8` into shallow-initialized `Box<T>`. + /// + /// This is different from a normal transmute because dataflow analysis will treat the box as + /// initialized but its content as uninitialized. Like other pointer casts, this in general + /// affects alias analysis. + ShallowInitBox(Operand, Ty), + + /// A CopyForDeref is equivalent to a read from a place at the + /// codegen level, but is treated specially by drop elaboration. When such a read happens, it + /// is guaranteed (via nature of the mir_opt `Derefer` in rustc_mir_transform/src/deref_separator) + /// that the only use of the returned value is a deref operation, immediately + /// followed by one or more projections. Drop elaboration treats this rvalue as if the + /// read never happened and just projects further. This allows simplifying various MIR + /// optimizations and codegen backends that previously had to handle deref operations anywhere + /// in a place. + CopyForDeref(Place), +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum StatementKind { + Assign(Place, Rvalue), + //FakeRead(Box<(FakeReadCause, Place)>), + //SetDiscriminant { + // place: Box<Place>, + // variant_index: VariantIdx, + //}, + Deinit(Place), + StorageLive(LocalId), + StorageDead(LocalId), + //Retag(RetagKind, Box<Place>), + //AscribeUserType(Place, UserTypeProjection, Variance), + //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 { + /// List of statements in this block. + pub statements: Vec<Statement>, + + /// Terminator for this block. + /// + /// N.B., this should generally ONLY be `None` during construction. + /// Therefore, you should generally access it via the + /// `terminator()` or `terminator_mut()` methods. The only + /// exception is that certain passes, such as `simplify_cfg`, swap + /// out the terminator temporarily with `None` while they continue + /// to recurse over the set of basic blocks. + pub terminator: Option<Terminator>, + + /// If true, this block lies on an unwind path. This is used + /// during codegen where distinct kinds of basic blocks may be + /// generated (particularly for MSVC cleanup). Unwind blocks must + /// only branch to other unwind blocks. + pub is_cleanup: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct MirBody { + pub basic_blocks: Arena<BasicBlock>, + pub locals: Arena<Local>, + pub start_block: BasicBlockId, + pub owner: DefWithBodyId, + pub arg_count: usize, + pub binding_locals: ArenaMap<BindingId, LocalId>, + pub param_locals: Vec<LocalId>, +} + +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/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/borrowck.rs new file mode 100644 index 000000000..c8729af86 --- /dev/null +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs new file mode 100644 index 000000000..c5d843d9e --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/eval.rs @@ -0,0 +1,1253 @@ +//! This module provides a MIR interpreter, which is used in const eval. + +use std::{borrow::Cow, collections::HashMap, iter}; + +use base_db::CrateId; +use chalk_ir::{ + fold::{FallibleTypeFolder, TypeFoldable, TypeSuperFoldable}, + DebruijnIndex, TyKind, +}; +use hir_def::{ + builtin_type::BuiltinType, + lang_item::{lang_attr, LangItem}, + layout::{Layout, LayoutError, RustcEnumVariantIdx, TagEncoding, Variants}, + AdtId, DefWithBodyId, EnumVariantId, FunctionId, HasModule, Lookup, VariantId, +}; +use intern::Interned; +use la_arena::ArenaMap; + +use crate::{ + consteval::{intern_const_scalar, ConstEvalError}, + db::HirDatabase, + from_placeholder_idx, + infer::{normalize, PointerCast}, + layout::layout_of_ty, + mapping::from_chalk, + method_resolution::lookup_impl_method, + CallableDefId, Const, ConstScalar, Interner, MemoryMap, Substitution, Ty, TyBuilder, TyExt, +}; + +use super::{ + const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError, + Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp, +}; + +pub struct Evaluator<'a> { + db: &'a dyn HirDatabase, + stack: Vec<u8>, + heap: Vec<u8>, + crate_id: CrateId, + // FIXME: This is a workaround, see the comment on `interpret_mir` + assert_placeholder_ty_is_unused: bool, + /// A general limit on execution, to prevent non terminating programs from breaking r-a main process + execution_limit: usize, + /// An additional limit on stack depth, to prevent stack overflow + stack_depth_limit: usize, +} + +#[derive(Debug, Clone, Copy)] +enum Address { + Stack(usize), + Heap(usize), +} + +use Address::*; + +struct Interval { + addr: Address, + size: usize, +} + +impl Interval { + fn new(addr: Address, size: usize) -> Self { + Self { addr, size } + } + + fn get<'a>(&self, memory: &'a Evaluator<'a>) -> Result<&'a [u8]> { + memory.read_memory(self.addr, self.size) + } +} + +enum IntervalOrOwned { + Owned(Vec<u8>), + Borrowed(Interval), +} +impl IntervalOrOwned { + pub(crate) fn to_vec(self, memory: &Evaluator<'_>) -> Result<Vec<u8>> { + Ok(match self { + IntervalOrOwned::Owned(o) => o, + IntervalOrOwned::Borrowed(b) => b.get(memory)?.to_vec(), + }) + } +} + +macro_rules! from_bytes { + ($ty:tt, $value:expr) => { + ($ty::from_le_bytes(match ($value).try_into() { + Ok(x) => x, + Err(_) => return Err(MirEvalError::TypeError("mismatched size")), + })) + }; +} + +impl Address { + fn from_bytes(x: &[u8]) -> Result<Self> { + Ok(Address::from_usize(from_bytes!(usize, x))) + } + + fn from_usize(x: usize) -> Self { + if x > usize::MAX / 2 { + Stack(usize::MAX - x) + } else { + Heap(x) + } + } + + fn to_bytes(&self) -> Vec<u8> { + usize::to_le_bytes(self.to_usize()).to_vec() + } + + fn to_usize(&self) -> usize { + let as_num = match self { + Stack(x) => usize::MAX - *x, + Heap(x) => *x, + }; + as_num + } + + fn map(&self, f: impl FnOnce(usize) -> usize) -> Address { + match self { + Stack(x) => Stack(f(*x)), + Heap(x) => Heap(f(*x)), + } + } + + fn offset(&self, offset: usize) -> Address { + self.map(|x| x + offset) + } +} + +#[derive(Clone, PartialEq, Eq)] +pub enum MirEvalError { + ConstEvalError(Box<ConstEvalError>), + LayoutError(LayoutError, Ty), + /// Means that code had type errors (or mismatched args) and we shouldn't generate mir in first place. + TypeError(&'static str), + /// Means that code had undefined behavior. We don't try to actively detect UB, but if it was detected + /// then use this type of error. + UndefinedBehavior(&'static str), + Panic, + MirLowerError(FunctionId, MirLowerError), + TypeIsUnsized(Ty, &'static str), + NotSupported(String), + InvalidConst(Const), + InFunction(FunctionId, Box<MirEvalError>), + ExecutionLimitExceeded, + StackOverflow, + TargetDataLayoutNotAvailable, +} + +impl std::fmt::Debug for MirEvalError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ConstEvalError(arg0) => f.debug_tuple("ConstEvalError").field(arg0).finish(), + Self::LayoutError(arg0, arg1) => { + f.debug_tuple("LayoutError").field(arg0).field(arg1).finish() + } + Self::TypeError(arg0) => f.debug_tuple("TypeError").field(arg0).finish(), + Self::UndefinedBehavior(arg0) => { + f.debug_tuple("UndefinedBehavior").field(arg0).finish() + } + Self::Panic => write!(f, "Panic"), + Self::TargetDataLayoutNotAvailable => write!(f, "TargetDataLayoutNotAvailable"), + Self::TypeIsUnsized(ty, it) => write!(f, "{ty:?} is unsized. {it} should be sized."), + Self::ExecutionLimitExceeded => write!(f, "execution limit exceeded"), + Self::StackOverflow => write!(f, "stack overflow"), + Self::MirLowerError(arg0, arg1) => { + f.debug_tuple("MirLowerError").field(arg0).field(arg1).finish() + } + Self::NotSupported(arg0) => f.debug_tuple("NotSupported").field(arg0).finish(), + Self::InvalidConst(arg0) => { + let data = &arg0.data(Interner); + f.debug_struct("InvalidConst").field("ty", &data.ty).field("value", &arg0).finish() + } + Self::InFunction(func, e) => { + let mut e = &**e; + let mut stack = vec![*func]; + while let Self::InFunction(f, next_e) = e { + e = &next_e; + stack.push(*f); + } + f.debug_struct("WithStack").field("error", e).field("stack", &stack).finish() + } + } + } +} + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirEvalError::NotSupported(format!($x))) + }; +} + +impl From<ConstEvalError> for MirEvalError { + fn from(value: ConstEvalError) -> Self { + match value { + _ => MirEvalError::ConstEvalError(Box::new(value)), + } + } +} + +type Result<T> = std::result::Result<T, MirEvalError>; + +struct Locals<'a> { + ptr: &'a ArenaMap<LocalId, Address>, + body: &'a MirBody, + subst: &'a Substitution, +} + +pub fn interpret_mir( + db: &dyn HirDatabase, + body: &MirBody, + // FIXME: This is workaround. Ideally, const generics should have a separate body (issue #7434), but now + // they share their body with their parent, so in MIR lowering we have locals of the parent body, which + // might have placeholders. With this argument, we (wrongly) assume that every placeholder type has + // a zero size, hoping that they are all outside of our current body. Even without a fix for #7434, we can + // (and probably should) do better here, for example by excluding bindings outside of the target expression. + assert_placeholder_ty_is_unused: bool, +) -> Result<Const> { + let ty = body.locals[return_slot()].ty.clone(); + let mut evaluator = + Evaluator::new(db, body.owner.module(db.upcast()).krate(), assert_placeholder_ty_is_unused); + let bytes = evaluator.interpret_mir_with_no_arg(&body)?; + let memory_map = evaluator.create_memory_map( + &bytes, + &ty, + &Locals { ptr: &ArenaMap::new(), body: &body, subst: &Substitution::empty(Interner) }, + )?; + return Ok(intern_const_scalar(ConstScalar::Bytes(bytes, memory_map), ty)); +} + +impl Evaluator<'_> { + pub fn new<'a>( + db: &'a dyn HirDatabase, + crate_id: CrateId, + assert_placeholder_ty_is_unused: bool, + ) -> Evaluator<'a> { + Evaluator { + stack: vec![0], + heap: vec![0], + db, + crate_id, + assert_placeholder_ty_is_unused, + stack_depth_limit: 100, + execution_limit: 100_000, + } + } + + fn place_addr(&self, p: &Place, locals: &Locals<'_>) -> Result<Address> { + Ok(self.place_addr_and_ty(p, locals)?.0) + } + + fn ptr_size(&self) -> usize { + match self.db.target_data_layout(self.crate_id) { + Some(x) => x.pointer_size.bytes_usize(), + None => 8, + } + } + + fn place_addr_and_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<(Address, Ty)> { + let mut addr = locals.ptr[p.local]; + let mut ty: Ty = + self.ty_filler(&locals.body.locals[p.local].ty, locals.subst, locals.body.owner)?; + for proj in &p.projection { + match proj { + ProjectionElem::Deref => { + ty = match &ty.data(Interner).kind { + TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(), + _ => { + return Err(MirEvalError::TypeError( + "Overloaded deref in MIR is disallowed", + )) + } + }; + let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?); + addr = Address::from_usize(x); + } + ProjectionElem::Index(op) => { + let offset = + from_bytes!(usize, self.read_memory(locals.ptr[*op], self.ptr_size())?); + match &ty.data(Interner).kind { + TyKind::Ref(_, _, inner) => match &inner.data(Interner).kind { + TyKind::Slice(inner) => { + ty = inner.clone(); + let ty_size = self.size_of_sized( + &ty, + locals, + "slice inner type should be sized", + )?; + let value = self.read_memory(addr, self.ptr_size() * 2)?; + addr = Address::from_bytes(&value[0..8])?.offset(ty_size * offset); + } + x => not_supported!("MIR index for ref type {x:?}"), + }, + TyKind::Array(inner, _) | TyKind::Slice(inner) => { + ty = inner.clone(); + let ty_size = self.size_of_sized( + &ty, + locals, + "array inner type should be sized", + )?; + addr = addr.offset(ty_size * offset); + } + x => not_supported!("MIR index for type {x:?}"), + } + } + &ProjectionElem::TupleField(f) => match &ty.data(Interner).kind { + TyKind::Tuple(_, subst) => { + let layout = self.layout(&ty)?; + ty = subst + .as_slice(Interner) + .get(f) + .ok_or(MirEvalError::TypeError("not enough tuple fields"))? + .assert_ty_ref(Interner) + .clone(); + let offset = layout.fields.offset(f).bytes_usize(); + addr = addr.offset(offset); + } + _ => return Err(MirEvalError::TypeError("Only tuple has tuple fields")), + }, + ProjectionElem::Field(f) => match &ty.data(Interner).kind { + TyKind::Adt(adt, subst) => { + let layout = self.layout_adt(adt.0, subst.clone())?; + let variant_layout = match &layout.variants { + Variants::Single { .. } => &layout, + Variants::Multiple { variants, .. } => { + &variants[match f.parent { + hir_def::VariantId::EnumVariantId(x) => { + RustcEnumVariantIdx(x.local_id) + } + _ => { + return Err(MirEvalError::TypeError( + "Multivariant layout only happens for enums", + )) + } + }] + } + }; + ty = self.db.field_types(f.parent)[f.local_id] + .clone() + .substitute(Interner, subst); + let offset = variant_layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + addr = addr.offset(offset); + } + _ => return Err(MirEvalError::TypeError("Only adt has fields")), + }, + ProjectionElem::ConstantIndex { .. } => { + not_supported!("constant index") + } + ProjectionElem::Subslice { .. } => not_supported!("subslice"), + ProjectionElem::OpaqueCast(_) => not_supported!("opaque cast"), + } + } + Ok((addr, ty)) + } + + fn layout(&self, ty: &Ty) -> Result<Layout> { + layout_of_ty(self.db, ty, self.crate_id) + .map_err(|e| MirEvalError::LayoutError(e, ty.clone())) + } + + fn layout_adt(&self, adt: AdtId, subst: Substitution) -> Result<Layout> { + self.db.layout_of_adt(adt, subst.clone()).map_err(|e| { + MirEvalError::LayoutError(e, TyKind::Adt(chalk_ir::AdtId(adt), subst).intern(Interner)) + }) + } + + fn place_ty<'a>(&'a self, p: &Place, locals: &'a Locals<'a>) -> Result<Ty> { + Ok(self.place_addr_and_ty(p, locals)?.1) + } + + fn operand_ty<'a>(&'a self, o: &'a Operand, locals: &'a Locals<'a>) -> Result<Ty> { + Ok(match o { + Operand::Copy(p) | Operand::Move(p) => self.place_ty(p, locals)?, + Operand::Constant(c) => c.data(Interner).ty.clone(), + }) + } + + fn interpret_mir( + &mut self, + body: &MirBody, + args: impl Iterator<Item = Vec<u8>>, + subst: Substitution, + ) -> Result<Vec<u8>> { + if let Some(x) = self.stack_depth_limit.checked_sub(1) { + self.stack_depth_limit = x; + } else { + return Err(MirEvalError::StackOverflow); + } + let mut current_block_idx = body.start_block; + let mut locals = Locals { ptr: &ArenaMap::new(), body: &body, subst: &subst }; + let (locals_ptr, stack_size) = { + let mut stack_ptr = self.stack.len(); + let addr = body + .locals + .iter() + .map(|(id, x)| { + 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))) + }) + .collect::<Result<ArenaMap<LocalId, _>>>()?; + let stack_size = stack_ptr - self.stack.len(); + (addr, stack_size) + }; + locals.ptr = &locals_ptr; + self.stack.extend(iter::repeat(0).take(stack_size)); + let mut remain_args = body.arg_count; + for ((_, addr), value) in locals_ptr.iter().skip(1).zip(args) { + self.write_memory(*addr, &value)?; + if remain_args == 0 { + return Err(MirEvalError::TypeError("more arguments provided")); + } + remain_args -= 1; + } + if remain_args > 0 { + return Err(MirEvalError::TypeError("not enough arguments provided")); + } + loop { + let current_block = &body.basic_blocks[current_block_idx]; + if let Some(x) = self.execution_limit.checked_sub(1) { + self.execution_limit = x; + } else { + return Err(MirEvalError::ExecutionLimitExceeded); + } + for statement in ¤t_block.statements { + 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)?; + } + StatementKind::Deinit(_) => not_supported!("de-init statement"), + StatementKind::StorageLive(_) + | StatementKind::StorageDead(_) + | StatementKind::Nop => (), + } + } + let Some(terminator) = current_block.terminator.as_ref() else { + not_supported!("block without terminator"); + }; + match terminator { + Terminator::Goto { target } => { + current_block_idx = *target; + } + Terminator::Call { + func, + args, + destination, + target, + cleanup: _, + from_hir_call: _, + } => { + let fn_ty = self.operand_ty(func, &locals)?; + match &fn_ty.data(Interner).kind { + TyKind::FnDef(def, generic_args) => { + let def: CallableDefId = from_chalk(self.db, *def); + let generic_args = self.subst_filler(generic_args, &locals); + match def { + CallableDefId::FunctionId(def) => { + let arg_bytes = args + .iter() + .map(|x| { + Ok(self + .eval_operand(x, &locals)? + .get(&self)? + .to_owned()) + }) + .collect::<Result<Vec<_>>>()? + .into_iter(); + let function_data = self.db.function_data(def); + let is_intrinsic = match &function_data.abi { + Some(abi) => *abi == Interned::new_str("rust-intrinsic"), + None => match def.lookup(self.db.upcast()).container { + hir_def::ItemContainerId::ExternBlockId(block) => { + let id = block.lookup(self.db.upcast()).id; + id.item_tree(self.db.upcast())[id.value] + .abi + .as_deref() + == Some("rust-intrinsic") + } + _ => false, + }, + }; + let result = if is_intrinsic { + self.exec_intrinsic( + function_data + .name + .as_text() + .unwrap_or_default() + .as_str(), + arg_bytes, + generic_args, + &locals, + )? + } else if let Some(x) = self.detect_lang_function(def) { + self.exec_lang_item(x, arg_bytes)? + } else { + let trait_env = { + let Some(d) = body.owner.as_generic_def_id() else { + not_supported!("trait resolving in non generic def id"); + }; + self.db.trait_environment(d) + }; + let (imp, generic_args) = lookup_impl_method( + self.db, + trait_env, + def, + generic_args.clone(), + ); + let generic_args = + self.subst_filler(&generic_args, &locals); + let def = imp.into(); + let mir_body = self + .db + .mir_body(def) + .map_err(|e| MirEvalError::MirLowerError(imp, e))?; + self.interpret_mir(&mir_body, arg_bytes, generic_args) + .map_err(|e| { + MirEvalError::InFunction(imp, Box::new(e)) + })? + }; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + CallableDefId::StructId(id) => { + let (size, variant_layout, tag) = self.layout_of_variant( + id.into(), + generic_args.clone(), + &locals, + )?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args, + &locals, + )?; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + CallableDefId::EnumVariantId(id) => { + let (size, variant_layout, tag) = self.layout_of_variant( + id.into(), + generic_args.clone(), + &locals, + )?; + let result = self.make_by_layout( + size, + &variant_layout, + tag, + args, + &locals, + )?; + let dest_addr = self.place_addr(destination, &locals)?; + self.write_memory(dest_addr, &result)?; + } + } + current_block_idx = + target.expect("broken mir, function without target"); + } + _ => not_supported!("unknown function type"), + } + } + Terminator::SwitchInt { discr, targets } => { + let val = u128::from_le_bytes(pad16( + self.eval_operand(discr, &locals)?.get(&self)?, + false, + )); + current_block_idx = targets.target_for_value(val); + } + Terminator::Return => { + let ty = body.locals[return_slot()].ty.clone(); + self.stack_depth_limit += 1; + return Ok(self + .read_memory( + locals.ptr[return_slot()], + self.size_of_sized(&ty, &locals, "return type")?, + )? + .to_owned()); + } + Terminator::Unreachable => { + return Err(MirEvalError::UndefinedBehavior("unreachable executed")) + } + _ => not_supported!("unknown terminator"), + } + } + } + + fn eval_rvalue<'a>( + &'a mut self, + r: &'a Rvalue, + locals: &'a Locals<'a>, + ) -> Result<IntervalOrOwned> { + use IntervalOrOwned::*; + Ok(match r { + Rvalue::Use(x) => Borrowed(self.eval_operand(x, locals)?), + Rvalue::Ref(_, p) => { + let addr = self.place_addr(p, locals)?; + Owned(addr.to_bytes()) + } + Rvalue::Len(_) => not_supported!("rvalue len"), + Rvalue::UnaryOp(op, val) => { + let mut c = self.eval_operand(val, locals)?.get(&self)?; + let mut ty = self.operand_ty(val, locals)?; + while let TyKind::Ref(_, _, z) = ty.kind(Interner) { + ty = z.clone(); + let size = self.size_of_sized(&ty, locals, "operand of unary op")?; + c = self.read_memory(Address::from_bytes(c)?, size)?; + } + let mut c = c.to_vec(); + if ty.as_builtin() == Some(BuiltinType::Bool) { + c[0] = 1 - c[0]; + } else { + match op { + UnOp::Not => c.iter_mut().for_each(|x| *x = !*x), + UnOp::Neg => { + c.iter_mut().for_each(|x| *x = !*x); + for k in c.iter_mut() { + let o; + (*k, o) = k.overflowing_add(1); + if !o { + break; + } + } + } + } + } + Owned(c) + } + Rvalue::CheckedBinaryOp(op, lhs, rhs) => { + let lc = self.eval_operand(lhs, locals)?; + let rc = self.eval_operand(rhs, locals)?; + let mut lc = lc.get(&self)?; + let mut rc = rc.get(&self)?; + let mut ty = self.operand_ty(lhs, locals)?; + while let TyKind::Ref(_, _, z) = ty.kind(Interner) { + ty = z.clone(); + let size = self.size_of_sized(&ty, locals, "operand of binary op")?; + lc = self.read_memory(Address::from_bytes(lc)?, size)?; + rc = self.read_memory(Address::from_bytes(rc)?, size)?; + } + let is_signed = matches!(ty.as_builtin(), Some(BuiltinType::Int(_))); + let l128 = i128::from_le_bytes(pad16(lc, is_signed)); + let r128 = i128::from_le_bytes(pad16(rc, is_signed)); + match op { + BinOp::Ge | BinOp::Gt | BinOp::Le | BinOp::Lt | BinOp::Eq | BinOp::Ne => { + let r = match op { + BinOp::Ge => l128 >= r128, + BinOp::Gt => l128 > r128, + BinOp::Le => l128 <= r128, + BinOp::Lt => l128 < r128, + BinOp::Eq => l128 == r128, + BinOp::Ne => l128 != r128, + _ => unreachable!(), + }; + let r = r as u8; + Owned(vec![r]) + } + BinOp::BitAnd + | BinOp::BitOr + | BinOp::BitXor + | BinOp::Add + | BinOp::Mul + | BinOp::Div + | BinOp::Rem + | BinOp::Sub => { + let r = match op { + BinOp::Add => l128.overflowing_add(r128).0, + BinOp::Mul => l128.overflowing_mul(r128).0, + BinOp::Div => l128.checked_div(r128).ok_or(MirEvalError::Panic)?, + BinOp::Rem => l128.checked_rem(r128).ok_or(MirEvalError::Panic)?, + BinOp::Sub => l128.overflowing_sub(r128).0, + BinOp::BitAnd => l128 & r128, + BinOp::BitOr => l128 | r128, + BinOp::BitXor => l128 ^ r128, + _ => unreachable!(), + }; + let r = r.to_le_bytes(); + for &k in &r[lc.len()..] { + if k != 0 && (k != 255 || !is_signed) { + return Err(MirEvalError::Panic); + } + } + Owned(r[0..lc.len()].into()) + } + BinOp::Shl | BinOp::Shr => { + let shift_amout = if r128 < 0 { + return Err(MirEvalError::Panic); + } else if r128 > 128 { + return Err(MirEvalError::Panic); + } else { + r128 as u8 + }; + let r = match op { + BinOp::Shl => l128 << shift_amout, + BinOp::Shr => l128 >> shift_amout, + _ => unreachable!(), + }; + Owned(r.to_le_bytes()[0..lc.len()].into()) + } + BinOp::Offset => not_supported!("offset binop"), + } + } + Rvalue::Discriminant(p) => { + let ty = self.place_ty(p, locals)?; + let bytes = self.eval_place(p, locals)?.get(&self)?; + let layout = self.layout(&ty)?; + match layout.variants { + Variants::Single { .. } => Owned(0u128.to_le_bytes().to_vec()), + Variants::Multiple { tag, tag_encoding, .. } => { + let Some(target_data_layout) = self.db.target_data_layout(self.crate_id) else { + not_supported!("missing target data layout"); + }; + let size = tag.size(&*target_data_layout).bytes_usize(); + let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field + match tag_encoding { + TagEncoding::Direct => { + let tag = &bytes[offset..offset + size]; + Owned(pad16(tag, false).to_vec()) + } + TagEncoding::Niche { untagged_variant, niche_start, .. } => { + let tag = &bytes[offset..offset + size]; + let candidate_discriminant = i128::from_le_bytes(pad16(tag, false)) + .wrapping_sub(niche_start as i128); + let enum_id = match ty.kind(Interner) { + TyKind::Adt(e, _) => match e.0 { + AdtId::EnumId(e) => e, + _ => not_supported!("Non enum with multi variant layout"), + }, + _ => not_supported!("Non adt with multi variant layout"), + }; + let enum_data = self.db.enum_data(enum_id); + let result = 'b: { + for (local_id, _) in enum_data.variants.iter() { + if candidate_discriminant + == self.db.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id, + })? + { + break 'b candidate_discriminant; + } + } + self.db.const_eval_discriminant(EnumVariantId { + parent: enum_id, + local_id: untagged_variant.0, + })? + }; + Owned(result.to_le_bytes().to_vec()) + } + } + } + } + } + Rvalue::ShallowInitBox(_, _) => not_supported!("shallow init box"), + Rvalue::CopyForDeref(_) => not_supported!("copy for deref"), + Rvalue::Aggregate(kind, values) => match kind { + AggregateKind::Array(_) => { + let mut r = vec![]; + for x in values { + let value = self.eval_operand(x, locals)?.get(&self)?; + r.extend(value); + } + Owned(r) + } + AggregateKind::Tuple(ty) => { + let layout = self.layout(&ty)?; + Owned(self.make_by_layout( + layout.size.bytes_usize(), + &layout, + None, + values, + locals, + )?) + } + AggregateKind::Union(x, f) => { + let layout = self.layout_adt((*x).into(), Substitution::empty(Interner))?; + let offset = layout + .fields + .offset(u32::from(f.local_id.into_raw()) as usize) + .bytes_usize(); + let op = self.eval_operand(&values[0], locals)?.get(&self)?; + let mut result = vec![0; layout.size.bytes_usize()]; + result[offset..offset + op.len()].copy_from_slice(op); + Owned(result) + } + AggregateKind::Adt(x, subst) => { + let (size, variant_layout, tag) = + self.layout_of_variant(*x, subst.clone(), locals)?; + Owned(self.make_by_layout(size, &variant_layout, tag, values, locals)?) + } + }, + Rvalue::Cast(kind, operand, target_ty) => match kind { + CastKind::PointerExposeAddress => not_supported!("exposing pointer address"), + CastKind::PointerFromExposedAddress => { + not_supported!("creating pointer from exposed address") + } + CastKind::Pointer(cast) => match cast { + PointerCast::Unsize => { + let current_ty = self.operand_ty(operand, locals)?; + match &target_ty.data(Interner).kind { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { + match &ty.data(Interner).kind { + TyKind::Slice(_) => match ¤t_ty.data(Interner).kind { + TyKind::Raw(_, ty) | TyKind::Ref(_, _, ty) => { + match &ty.data(Interner).kind { + TyKind::Array(_, size) => { + let addr = self + .eval_operand(operand, locals)? + .get(&self)?; + let len = const_as_usize(size); + let mut r = Vec::with_capacity(16); + r.extend(addr.iter().copied()); + r.extend(len.to_le_bytes().into_iter()); + Owned(r) + } + _ => { + not_supported!("slice unsizing from non arrays") + } + } + } + _ => not_supported!("slice unsizing from non pointers"), + }, + TyKind::Dyn(_) => not_supported!("dyn pointer unsize cast"), + _ => not_supported!("unknown unsized cast"), + } + } + _ => not_supported!("unsized cast on unknown pointer type"), + } + } + x => not_supported!("pointer cast {x:?}"), + }, + CastKind::DynStar => not_supported!("dyn star cast"), + CastKind::IntToInt => { + // FIXME: handle signed cast + let current = pad16(self.eval_operand(operand, locals)?.get(&self)?, false); + let dest_size = + self.size_of_sized(target_ty, locals, "destination of int to int cast")?; + Owned(current[0..dest_size].to_vec()) + } + CastKind::FloatToInt => not_supported!("float to int cast"), + CastKind::FloatToFloat => not_supported!("float to float cast"), + CastKind::IntToFloat => not_supported!("float to int cast"), + CastKind::PtrToPtr => not_supported!("ptr to ptr cast"), + CastKind::FnPtrToPtr => not_supported!("fn ptr to ptr cast"), + }, + }) + } + + fn layout_of_variant( + &mut self, + x: VariantId, + subst: Substitution, + locals: &Locals<'_>, + ) -> Result<(usize, Layout, Option<(usize, usize, i128)>)> { + let adt = x.adt_id(); + if let DefWithBodyId::VariantId(f) = locals.body.owner { + if let VariantId::EnumVariantId(x) = x { + if AdtId::from(f.parent) == adt { + // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and + // infinite sized type errors) we use a dummy layout + let i = self.db.const_eval_discriminant(x)?; + return Ok((16, self.layout(&TyBuilder::unit())?, Some((0, 16, i)))); + } + } + } + let layout = self.layout_adt(adt, subst)?; + Ok(match layout.variants { + Variants::Single { .. } => (layout.size.bytes_usize(), layout, None), + Variants::Multiple { variants, tag, tag_encoding, .. } => { + let cx = self + .db + .target_data_layout(self.crate_id) + .ok_or(MirEvalError::TargetDataLayoutNotAvailable)?; + let enum_variant_id = match x { + VariantId::EnumVariantId(x) => x, + _ => not_supported!("multi variant layout for non-enums"), + }; + let rustc_enum_variant_idx = RustcEnumVariantIdx(enum_variant_id.local_id); + let mut discriminant = self.db.const_eval_discriminant(enum_variant_id)?; + let variant_layout = variants[rustc_enum_variant_idx].clone(); + let have_tag = match tag_encoding { + TagEncoding::Direct => true, + TagEncoding::Niche { untagged_variant, niche_variants: _, niche_start } => { + discriminant = discriminant.wrapping_add(niche_start as i128); + untagged_variant != rustc_enum_variant_idx + } + }; + ( + layout.size.bytes_usize(), + variant_layout, + if have_tag { + Some(( + layout.fields.offset(0).bytes_usize(), + tag.size(&*cx).bytes_usize(), + discriminant, + )) + } else { + None + }, + ) + } + }) + } + + fn make_by_layout( + &mut self, + size: usize, // Not neccessarily equal to variant_layout.size + variant_layout: &Layout, + tag: Option<(usize, usize, i128)>, + values: &Vec<Operand>, + locals: &Locals<'_>, + ) -> Result<Vec<u8>> { + let mut result = vec![0; size]; + if let Some((offset, size, value)) = tag { + result[offset..offset + size].copy_from_slice(&value.to_le_bytes()[0..size]); + } + for (i, op) in values.iter().enumerate() { + let offset = variant_layout.fields.offset(i).bytes_usize(); + let op = self.eval_operand(op, locals)?.get(&self)?; + result[offset..offset + op.len()].copy_from_slice(op); + } + Ok(result) + } + + fn eval_operand(&mut self, x: &Operand, locals: &Locals<'_>) -> Result<Interval> { + Ok(match x { + Operand::Copy(p) | Operand::Move(p) => self.eval_place(p, locals)?, + Operand::Constant(konst) => { + let data = &konst.data(Interner); + match &data.value { + chalk_ir::ConstValue::BoundVar(b) => { + let c = locals + .subst + .as_slice(Interner) + .get(b.index) + .ok_or(MirEvalError::TypeError("missing generic arg"))? + .assert_const_ref(Interner); + self.eval_operand(&Operand::Constant(c.clone()), locals)? + } + chalk_ir::ConstValue::InferenceVar(_) => { + not_supported!("inference var constant") + } + chalk_ir::ConstValue::Placeholder(_) => not_supported!("placeholder constant"), + chalk_ir::ConstValue::Concrete(c) => match &c.interned { + ConstScalar::Bytes(v, memory_map) => { + let mut v: Cow<'_, [u8]> = Cow::Borrowed(v); + let patch_map = memory_map.transform_addresses(|b| { + let addr = self.heap_allocate(b.len()); + self.write_memory(addr, b)?; + Ok(addr.to_usize()) + })?; + let size = self.size_of(&data.ty, locals)?.unwrap_or(v.len()); + if size != v.len() { + // Handle self enum + if size == 16 && v.len() < 16 { + v = Cow::Owned(pad16(&v, false).to_vec()); + } else if size < 16 && v.len() == 16 { + v = Cow::Owned(v[0..size].to_vec()); + } else { + return Err(MirEvalError::InvalidConst(konst.clone())); + } + } + let addr = self.heap_allocate(size); + self.write_memory(addr, &v)?; + self.patch_addresses(&patch_map, addr, &data.ty, locals)?; + Interval::new(addr, size) + } + ConstScalar::Unknown => not_supported!("evaluating unknown const"), + }, + } + } + }) + } + + fn eval_place(&mut self, p: &Place, locals: &Locals<'_>) -> Result<Interval> { + let addr = self.place_addr(p, locals)?; + Ok(Interval::new( + addr, + self.size_of_sized(&self.place_ty(p, locals)?, locals, "type of this place")?, + )) + } + + fn read_memory(&self, addr: Address, size: usize) -> Result<&[u8]> { + let (mem, pos) = match addr { + Stack(x) => (&self.stack, x), + Heap(x) => (&self.heap, x), + }; + mem.get(pos..pos + size).ok_or(MirEvalError::UndefinedBehavior("out of bound memory read")) + } + + fn write_memory(&mut self, addr: Address, r: &[u8]) -> Result<()> { + let (mem, pos) = match addr { + Stack(x) => (&mut self.stack, x), + Heap(x) => (&mut self.heap, x), + }; + mem.get_mut(pos..pos + r.len()) + .ok_or(MirEvalError::UndefinedBehavior("out of bound memory write"))? + .copy_from_slice(r); + Ok(()) + } + + fn size_of(&self, ty: &Ty, locals: &Locals<'_>) -> Result<Option<usize>> { + if let DefWithBodyId::VariantId(f) = locals.body.owner { + if let Some((adt, _)) = ty.as_adt() { + if AdtId::from(f.parent) == adt { + // Computing the exact size of enums require resolving the enum discriminants. In order to prevent loops (and + // infinite sized type errors) we use a dummy size + return Ok(Some(16)); + } + } + } + let ty = &self.ty_filler(ty, locals.subst, locals.body.owner)?; + let layout = self.layout(ty); + if self.assert_placeholder_ty_is_unused { + if matches!(layout, Err(MirEvalError::LayoutError(LayoutError::HasPlaceholder, _))) { + return Ok(Some(0)); + } + } + let layout = layout?; + Ok(layout.is_sized().then(|| layout.size.bytes_usize())) + } + + /// A version of `self.size_of` which returns error if the type is unsized. `what` argument should + /// be something that complete this: `error: type {ty} was unsized. {what} should be sized` + fn size_of_sized(&self, ty: &Ty, locals: &Locals<'_>, what: &'static str) -> Result<usize> { + match self.size_of(ty, locals)? { + Some(x) => Ok(x), + None => Err(MirEvalError::TypeIsUnsized(ty.clone(), what)), + } + } + + /// Uses `ty_filler` to fill an entire subst + fn subst_filler(&self, subst: &Substitution, locals: &Locals<'_>) -> Substitution { + Substitution::from_iter( + Interner, + subst.iter(Interner).map(|x| match x.data(Interner) { + chalk_ir::GenericArgData::Ty(ty) => { + let Ok(ty) = self.ty_filler(ty, locals.subst, locals.body.owner) else { + return x.clone(); + }; + chalk_ir::GenericArgData::Ty(ty).intern(Interner) + } + _ => x.clone(), + }), + ) + } + + /// This function substitutes placeholders of the body with the provided subst, effectively plays + /// the rule of monomorphization. In addition to placeholders, it substitutes opaque types (return + /// position impl traits) with their underlying type. + fn ty_filler(&self, ty: &Ty, subst: &Substitution, owner: DefWithBodyId) -> Result<Ty> { + struct Filler<'a> { + db: &'a dyn HirDatabase, + subst: &'a Substitution, + skip_params: usize, + } + impl FallibleTypeFolder<Interner> for Filler<'_> { + type Error = MirEvalError; + + fn as_dyn(&mut self) -> &mut dyn FallibleTypeFolder<Interner, Error = Self::Error> { + self + } + + fn interner(&self) -> Interner { + Interner + } + + fn try_fold_ty( + &mut self, + ty: Ty, + outer_binder: DebruijnIndex, + ) -> std::result::Result<Ty, Self::Error> { + match ty.kind(Interner) { + TyKind::OpaqueType(id, subst) => { + let impl_trait_id = self.db.lookup_intern_impl_trait_id((*id).into()); + match impl_trait_id { + crate::ImplTraitId::ReturnTypeImplTrait(func, idx) => { + let infer = self.db.infer(func.into()); + let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; + filler.try_fold_ty(infer.type_of_rpit[idx].clone(), outer_binder) + } + crate::ImplTraitId::AsyncBlockTypeImplTrait(_, _) => { + not_supported!("async block impl trait"); + } + } + } + _ => ty.try_super_fold_with(self.as_dyn(), outer_binder), + } + } + + fn try_fold_free_placeholder_ty( + &mut self, + idx: chalk_ir::PlaceholderIndex, + _outer_binder: DebruijnIndex, + ) -> std::result::Result<Ty, Self::Error> { + let x = from_placeholder_idx(self.db, idx); + Ok(self + .subst + .as_slice(Interner) + .get((u32::from(x.local_id.into_raw()) as usize) + self.skip_params) + .and_then(|x| x.ty(Interner)) + .ok_or(MirEvalError::TypeError("Generic arg not provided"))? + .clone()) + } + } + let filler = &mut Filler { db: self.db, subst, skip_params: 0 }; + Ok(normalize(self.db, owner, ty.clone().try_fold_with(filler, DebruijnIndex::INNERMOST)?)) + } + + fn heap_allocate(&mut self, s: usize) -> Address { + let pos = self.heap.len(); + self.heap.extend(iter::repeat(0).take(s)); + Address::Heap(pos) + } + + pub fn interpret_mir_with_no_arg(&mut self, body: &MirBody) -> Result<Vec<u8>> { + self.interpret_mir(&body, vec![].into_iter(), Substitution::empty(Interner)) + } + + fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> { + 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> { + // FIXME: support indirect references + let mut mm = MemoryMap::default(); + match ty.kind(Interner) { + TyKind::Ref(_, _, t) => { + let size = self.size_of(t, locals)?; + match size { + Some(size) => { + let addr_usize = from_bytes!(usize, bytes); + mm.insert( + addr_usize, + self.read_memory(Address::from_usize(addr_usize), size)?.to_vec(), + ) + } + None => { + let element_size = match t.kind(Interner) { + TyKind::Str => 1, + TyKind::Slice(t) => { + self.size_of_sized(t, locals, "slice inner type")? + } + _ => return Ok(mm), // FIXME: support other kind of unsized types + }; + let (addr, meta) = bytes.split_at(bytes.len() / 2); + let size = element_size * from_bytes!(usize, meta); + let addr = Address::from_bytes(addr)?; + mm.insert(addr.to_usize(), self.read_memory(addr, size)?.to_vec()); + } + } + } + _ => (), + } + Ok(mm) + } + + fn patch_addresses( + &mut self, + patch_map: &HashMap<usize, usize>, + addr: Address, + ty: &Ty, + locals: &Locals<'_>, + ) -> Result<()> { + // FIXME: support indirect references + let my_size = self.size_of_sized(ty, locals, "value to patch address")?; + match ty.kind(Interner) { + TyKind::Ref(_, _, t) => { + let size = self.size_of(t, locals)?; + match size { + Some(_) => { + let current = from_bytes!(usize, self.read_memory(addr, my_size)?); + if let Some(x) = patch_map.get(¤t) { + self.write_memory(addr, &x.to_le_bytes())?; + } + } + None => { + let current = from_bytes!(usize, self.read_memory(addr, my_size / 2)?); + if let Some(x) = patch_map.get(¤t) { + self.write_memory(addr, &x.to_le_bytes())?; + } + } + } + } + _ => (), + } + Ok(()) + } + + fn exec_intrinsic( + &self, + as_str: &str, + _arg_bytes: impl Iterator<Item = Vec<u8>>, + generic_args: Substitution, + locals: &Locals<'_>, + ) -> Result<Vec<u8>> { + match as_str { + "size_of" => { + let Some(ty) = generic_args.as_slice(Interner).get(0).and_then(|x| x.ty(Interner)) else { + return Err(MirEvalError::TypeError("size_of generic arg is not provided")); + }; + let size = self.size_of(ty, locals)?; + match size { + Some(x) => Ok(x.to_le_bytes().to_vec()), + None => return Err(MirEvalError::TypeError("size_of arg is unsized")), + } + } + _ => not_supported!("unknown intrinsic {as_str}"), + } + } + + pub(crate) fn exec_lang_item( + &self, + x: LangItem, + mut args: std::vec::IntoIter<Vec<u8>>, + ) -> Result<Vec<u8>> { + use LangItem::*; + match x { + PanicFmt | BeginPanic => Err(MirEvalError::Panic), + SliceLen => { + let arg = args + .next() + .ok_or(MirEvalError::TypeError("argument of <[T]>::len() is not provided"))?; + let ptr_size = arg.len() / 2; + Ok(arg[ptr_size..].into()) + } + x => not_supported!("Executing lang item {x:?}"), + } + } +} + +pub fn pad16(x: &[u8], is_signed: bool) -> [u8; 16] { + let is_negative = is_signed && x.last().unwrap_or(&0) > &128; + let fill_with = if is_negative { 255 } else { 0 }; + x.iter() + .copied() + .chain(iter::repeat(fill_with)) + .take(16) + .collect::<Vec<u8>>() + .try_into() + .expect("iterator take is not working") +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs new file mode 100644 index 000000000..c4dd7c0ac --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs @@ -0,0 +1,1581 @@ +//! This module generates a polymorphic MIR from a hir body + +use std::{iter, mem, sync::Arc}; + +use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; +use hir_def::{ + body::Body, + expr::{ + 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, 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, + /// `None` for loops that are not terminating + end: Option<BasicBlockId>, +} + +struct MirLowerCtx<'a> { + result: MirBody, + owner: DefWithBodyId, + current_loop_blocks: Option<LoopBlocks>, + discr_temp: Option<Place>, + db: &'a dyn HirDatabase, + body: &'a Body, + infer: &'a InferenceResult, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MirLowerError { + ConstEvalError(Box<ConstEvalError>), + LayoutError(LayoutError), + IncompleteExpr, + 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 { + ($x: expr) => { + return Err(MirLowerError::NotSupported(format!($x))) + }; +} + +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 { + ConstEvalError::MirLowerError(e) => e, + _ => MirLowerError::ConstEvalError(Box::new(value)), + } + } +} + +impl From<LayoutError> for MirLowerError { + fn from(value: LayoutError) -> Self { + MirLowerError::LayoutError(value) + } +} + +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(_)) { + 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<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(Some((self.lower_literal_to_operand(ty, l)?, 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_place_with_adjust( + &mut self, + expr_id: ExprId, + 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), + } + } + + fn lower_expr_to_place( + &mut self, + expr_id: ExprId, + place: Place, + prev_block: BasicBlockId, + ) -> 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) + } + + fn lower_expr_to_place_without_adjust( + &mut self, + expr_id: ExprId, + place: Place, + mut current: 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_else(unresolved_name)?; + let pr = match pr { + ResolveValueResult::ValueNs(v) => v, + ResolveValueResult::Partial(..) => { + if let Some(assoc) = self + .infer + .assoc_resolutions_for_expr(expr_id) + { + 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 { + ValueNs::LocalBinding(pat_id) => { + self.push_assignment( + current, + place, + Operand::Copy(self.result.binding_locals[pat_id].into()).into(), + expr_id.into(), + ); + Ok(Some(current)) + } + ValueNs::ConstId(const_id) => { + self.lower_const(const_id, current, place, expr_id.into())?; + Ok(Some(current)) + } + ValueNs::EnumVariantId(variant_id) => { + let ty = self.infer.type_of_expr[expr_id].clone(); + 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 { + not_supported!("owner without generic def id"); + }; + let gen = generics(self.db.upcast(), def); + let ty = self.expr_ty(expr_id); + self.push_assignment( + current, + place, + Operand::Constant( + ConstData { + ty, + value: chalk_ir::ConstValue::BoundVar(BoundVar::new( + DebruijnIndex::INNERMOST, + gen.param_idx(p.into()).ok_or(MirLowerError::TypeError( + "fail to lower const generic param", + ))?, + )), + } + .intern(Interner), + ) + .into(), + expr_id.into(), + ); + Ok(Some(current)) + } + ValueNs::StructId(_) => { + // It's probably a unit struct or a zero sized function, so no action is needed. + Ok(Some(current)) + } + x => { + not_supported!("unknown name {x:?} in value name space"); + } + } + } + Expr::If { condition, then_branch, else_branch } => { + 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_of_then = + self.lower_expr_to_place(*then_branch, place.clone(), start_of_then)?; + 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 { + discr, + targets: SwitchTargets::static_if(1, start_of_then, start_of_else), + }, + ); + Ok(self.merge_blocks(end_of_then, end_of_else)) + } + Expr::Let { pat, expr } => { + 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_after_adjustments(*expr), + *pat, + BindingAnnotation::Unannotated, + )?; + 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(), + MirSpan::Unknown, + )?; + } + Ok(self.merge_blocks(Some(then_target), else_target)) + } + Expr::Unsafe { id: _, statements, tail } => { + self.lower_block_to_place(None, statements, current, *tail, place) + } + 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| { + 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| { + 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, + Terminator::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, after_cond, end), + }, + ); + if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? { + this.set_goto(block, begin); + } + Ok(()) + }) + } + &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_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_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id)) + } + TyKind::Scalar(_) + | TyKind::Tuple(_, _) + | TyKind::Array(_, _) + | TyKind::Adt(_, _) + | TyKind::Str + | TyKind::Foreign(_) + | TyKind::Slice(_) => { + return Err(MirLowerError::TypeError("function call on data type")) + } + TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition), + TyKind::AssociatedType(_, _) + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::OpaqueType(_, _) + | TyKind::Never + | TyKind::Closure(_, _) + | TyKind::Generator(_, _) + | TyKind::GeneratorWitness(_, _) + | TyKind::Placeholder(_) + | TyKind::Dyn(_) + | TyKind::Alias(_) + | TyKind::Function(_) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => not_supported!("dynamic function call"), + } + } + Expr::MethodCall { receiver, args, .. } => { + let (func_id, generic_args) = + 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_and_args( + func, + iter::once(*receiver).chain(args.iter().copied()), + place, + current, + self.is_uninhabited(expr_id), + ) + } + Expr::Match { expr, arms } => { + 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"); + } + let (then, otherwise) = self.pattern_match( + current, + None, + cond_place.clone(), + cond_ty.clone(), + *pat, + BindingAnnotation::Unannotated, + )?; + 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 => { + // The current pattern was irrefutable, so there is no need to generate code + // for the rest of patterns + break; + } + } + } + if self.is_unterminated(current) { + self.set_terminator(current, Terminator::Unreachable); + } + Ok(end) + } + Expr::Continue { label } => match label { + Some(_) => not_supported!("continue with label"), + None => { + let loop_data = + self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?; + self.set_goto(current, loop_data.begin); + Ok(None) + } + }, + Expr::Break { expr, label } => { + if expr.is_some() { + not_supported!("break with value"); + } + match label { + Some(_) => not_supported!("break with label"), + None => { + let end = + self.current_loop_end()?; + self.set_goto(current, end); + Ok(None) + } + } + } + Expr::Return { expr } => { + if let Some(expr) = expr { + 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(None) + } + Expr::Yield { .. } => not_supported!("yield"), + Expr::RecordLit { fields, path, .. } => { + let variant_id = self + .infer + .variant_resolution_for_expr(expr_id) + .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"), + }; + let variant_data = variant_id.variant_data(self.db.upcast()); + match variant_id { + VariantId::EnumVariantId(_) | VariantId::StructId(_) => { + 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::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( + current, + place, + Rvalue::Aggregate( + AggregateKind::Adt(variant_id, subst), + operands.into_iter().map(|x| x).collect::<Option<_>>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + ), + expr_id.into(), + ); + 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::UnresolvedField)?; + let mut place = place; + place + .projection + .push(PlaceElem::Field(FieldId { parent: union_id.into(), local_id })); + self.lower_expr_to_place(*expr, place, current) + } + } + } + Expr::Await { .. } => not_supported!("await"), + Expr::Try { .. } => not_supported!("? operator"), + Expr::Yeet { .. } => not_supported!("yeet"), + Expr::TryBlock { .. } => not_supported!("try block"), + Expr::Async { .. } => not_supported!("async block"), + Expr::Const { .. } => not_supported!("anonymous const block"), + Expr::Cast { expr, type_ref: _ } => { + 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(Some(current)) + } + Expr::Ref { expr, rawness: _, mutability } => { + 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), expr_id.into()); + Ok(Some(current)) + } + Expr::Box { .. } => not_supported!("box expression"), + Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::expr::UnaryOp::Deref, .. } => { + let Some((p, current)) = self.lower_expr_as_place_without_adjust(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)?; + if let hir_def::expr::BinaryOp::Assignment { op } = op { + if op.is_some() { + not_supported!("assignment with arith op (like +=)"); + } + let Some((lhs_place, current)) = + self.lower_expr_as_place(current, *lhs, false)? + 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, lhs_place, rhs_op.into(), expr_id.into()); + return Ok(Some(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, + Rvalue::CheckedBinaryOp( + match op { + hir_def::expr::BinaryOp::LogicOp(op) => match op { + hir_def::expr::LogicOp::And => BinOp::BitAnd, // FIXME: make these short circuit + hir_def::expr::LogicOp::Or => BinOp::BitOr, + }, + hir_def::expr::BinaryOp::ArithOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::CmpOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::Assignment { .. } => unreachable!(), // handled above + }, + lhs_op, + rhs_op, + ), + expr_id.into(), + ); + Ok(Some(current)) + } + Expr::Range { .. } => not_supported!("range"), + Expr::Closure { .. } => not_supported!("closure"), + Expr::Tuple { exprs, is_assignee_expr: _ } => { + let Some(values) = exprs + .iter() + .map(|x| { + let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else { + return Ok(None); + }; + current = c; + Ok(Some(o)) + }) + .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, expr_id.into()); + Ok(Some(current)) + } + Expr::Array(l) => match l { + Array::ElementList { elements, .. } => { + let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind { + TyKind::Array(ty, _) => ty.clone(), + _ => { + return Err(MirLowerError::TypeError( + "Array expression with non array type", + )) + } + }; + let Some(values) = elements + .iter() + .map(|x| { + let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else { + return Ok(None); + }; + current = c; + Ok(Some(o)) + }) + .collect::<Result<Option<_>>>()? + else { + return Ok(None); + }; + let r = Rvalue::Aggregate( + AggregateKind::Array(elem_ty), + values, + ); + 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(), expr_id.into()); + Ok(Some(current)) + } + Expr::Underscore => not_supported!("underscore"), + } + } + + 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!("") + } + Ok(()) + } + + fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> { + let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())? + .size + .bytes_usize(); + let bytes = match l { + hir_def::expr::Literal::String(b) => { + let b = b.as_bytes(); + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::ByteString(b) => { + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::Char(c) => u32::from(*c).to_le_bytes().into(), + hir_def::expr::Literal::Bool(b) => vec![*b as u8], + hir_def::expr::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Float(f, _) => match size { + 8 => f.into_f64().to_le_bytes().into(), + 4 => f.into_f32().to_le_bytes().into(), + _ => { + return Err(MirLowerError::TypeError("float with size other than 4 or 8 bytes")) + } + }, + }; + Ok(Operand::from_concrete_const(bytes, MemoryMap::default(), ty)) + } + + fn new_basic_block(&mut self) -> BasicBlockId { + self.result.basic_blocks.alloc(BasicBlock::default()) + } + + fn lower_const( + &mut self, + const_id: hir_def::ConstId, + prev_block: BasicBlockId, + place: Place, + span: MirSpan, + ) -> Result<()> { + let c = self.db.const_eval(const_id)?; + self.write_const_to_place(c, prev_block, place, span) + } + + fn write_const_to_place( + &mut self, + c: Const, + prev_block: BasicBlockId, + place: Place, + span: MirSpan, + ) -> Result<()> { + self.push_assignment(prev_block, place, Operand::Constant(c).into(), span); + Ok(()) + } + + fn write_bytes_to_place( + &mut self, + prev_block: BasicBlockId, + place: Place, + cv: Vec<u8>, + ty: Ty, + span: MirSpan, + ) -> Result<()> { + self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into(), span); + Ok(()) + } + + fn lower_enum_variant( + &mut self, + variant_id: EnumVariantId, + prev_block: BasicBlockId, + place: Place, + ty: Ty, + fields: Vec<Operand>, + span: MirSpan, + ) -> Result<BasicBlockId> { + let subst = match ty.kind(Interner) { + TyKind::Adt(_, subst) => subst.clone(), + _ => not_supported!("Non ADT enum"), + }; + self.push_assignment( + prev_block, + place, + Rvalue::Aggregate(AggregateKind::Adt(variant_id.into(), subst), fields), + span, + ); + Ok(prev_block) + } + + fn lower_call_and_args( + &mut self, + func: Operand, + args: impl Iterator<Item = ExprId>, + place: Place, + mut current: BasicBlockId, + is_uninhabited: bool, + ) -> Result<Option<BasicBlockId>> { + let Some(args) = args + .map(|arg| { + if let Some((temp, c)) = self.lower_expr_to_some_operand(arg, current)? { + current = c; + Ok(Some(temp)) + } else { + Ok(None) + } + }) + .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: b, + cleanup: None, + from_hir_call: true, + }, + ); + Ok(b) + } + + fn is_unterminated(&mut self, source: BasicBlockId) -> bool { + self.result.basic_blocks[source].terminator.is_none() + } + + fn set_terminator(&mut self, source: BasicBlockId, terminator: Terminator) { + self.result.basic_blocks[source].terminator = Some(terminator); + } + + fn set_goto(&mut self, source: BasicBlockId, target: BasicBlockId) { + self.set_terminator(source, Terminator::Goto { target }); + } + + fn expr_ty(&self, e: ExprId) -> Ty { + self.infer[e].clone() + } + + 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 + /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which + /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the + /// mismatched path block is `None`. + /// + /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with + /// `current_else` argument to save an unneccessary jump. If `current_else` isn't `None`, the result mismatched path + /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block, + /// so it should be an empty block. + fn pattern_match( + &mut self, + mut current: BasicBlockId, + mut current_else: Option<BasicBlockId>, + mut cond_place: Place, + mut cond_ty: Ty, + pattern: PatId, + mut binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { + Ok(match &self.body.pats[pattern] { + Pat::Missing => return Err(MirLowerError::IncompleteExpr), + Pat::Wild => (current, current_else), + Pat::Tuple { args, ellipsis } => { + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Tuple(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non tuple type matched with tuple pattern", + )) + } + }; + self.pattern_match_tuple_like( + current, + current_else, + args.iter().enumerate().map(|(i, x)| { + ( + PlaceElem::TupleField(i), + *x, + subst.at(Interner, i).assert_ty_ref(Interner).clone(), + ) + }), + *ellipsis, + &cond_place, + binding_mode, + )? + } + 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; + } + } + } + if !finished { + let ce = *current_else.get_or_insert_with(|| self.new_basic_block()); + self.set_goto(current, ce); + } + (then_target, current_else) + } + Pat::Record { .. } => not_supported!("record pattern"), + Pat::Range { .. } => not_supported!("range pattern"), + Pat::Slice { .. } => not_supported!("slice 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()); + match &self.body.exprs[*l] { + Expr::Literal(l) => match l { + hir_def::expr::Literal::Int(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if( + *x as u128, + then_target, + else_target, + ), + }, + ); + } + hir_def::expr::Literal::Uint(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if(*x, then_target, else_target), + }, + ); + } + _ => not_supported!("non int path literal"), + }, + _ => not_supported!("expression path literal"), + } + (then_target, Some(else_target)) + } + 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, + current_else, + cond_place.clone(), + cond_ty, + *subpat, + binding_mode, + )? + } + if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) { + binding_mode = mode; + } + self.push_storage_live(*id, current); + self.push_assignment( + current, + target_place.into(), + match binding_mode { + BindingAnnotation::Unannotated | BindingAnnotation::Mutable => { + Operand::Copy(cond_place).into() + } + BindingAnnotation::Ref => Rvalue::Ref(BorrowKind::Shared, cond_place), + BindingAnnotation::RefMut => Rvalue::Ref( + BorrowKind::Mut { allow_two_phase_borrow: false }, + cond_place, + ), + }, + pattern.into(), + ); + (current, current_else) + } + Pat::TupleStruct { path: _, args, ellipsis } => { + 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, + args, + ellipsis, + )? + } + Pat::Ref { .. } => not_supported!("& pattern"), + Pat::Box { .. } => not_supported!("box pattern"), + Pat::ConstBlock(_) => not_supported!("const block pattern"), + }) + } + + 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, + mut current_else: Option<BasicBlockId>, + args: impl Iterator<Item = (PlaceElem, PatId, Ty)>, + ellipsis: Option<usize>, + cond_place: &Place, + binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { + if ellipsis.is_some() { + not_supported!("tuple like pattern with ellipsis"); + } + for (proj, arg, ty) in args { + let mut cond_place = cond_place.clone(); + cond_place.projection.push(proj); + (current, current_else) = + self.pattern_match(current, current_else, cond_place, ty, arg, binding_mode)?; + } + Ok((current, current_else)) + } + + fn discr_temp_place(&mut self) -> Place { + match &self.discr_temp { + Some(x) => x.clone(), + None => { + let tmp: Place = + self.temp(TyBuilder::discr_ty()).expect("discr_ty is never unsized").into(); + self.discr_temp = Some(tmp.clone()); + tmp + } + } + } + + fn lower_loop( + &mut self, + prev_block: BasicBlockId, + label: Option<LabelId>, + 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 prev = + mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end: None })); + self.set_goto(prev_block, begin); + 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` statement for the binding, and applies changes to add `StorageDead` in + /// the appropriated places. + fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) { + // 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)); + } + + 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); + } + } + } + } else { + self.body.walk_bindings_in_pat(*pat, |b| { + self.push_storage_live(b, current); + }); + } + } + 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( + cond_ty: &mut Ty, + binding_mode: &mut BindingAnnotation, + cond_place: &mut Place, +) { + while let Some((ty, _, mu)) = cond_ty.as_reference() { + if mu == Mutability::Mut && *binding_mode != BindingAnnotation::Ref { + *binding_mode = BindingAnnotation::RefMut; + } else { + *binding_mode = BindingAnnotation::Ref; + } + *cond_ty = ty.clone(); + cond_place.projection.push(ProjectionElem::Deref); + } +} + +fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> { + Ok(match (source_ty.kind(Interner), target_ty.kind(Interner)) { + (TyKind::Scalar(s), TyKind::Scalar(t)) => match (s, t) { + (chalk_ir::Scalar::Float(_), chalk_ir::Scalar::Float(_)) => CastKind::FloatToFloat, + (chalk_ir::Scalar::Float(_), _) => CastKind::FloatToInt, + (_, chalk_ir::Scalar::Float(_)) => CastKind::IntToFloat, + (_, _) => CastKind::IntToInt, + }, + // Enum to int casts + (TyKind::Scalar(_), TyKind::Adt(..)) | (TyKind::Adt(..), TyKind::Scalar(_)) => { + CastKind::IntToInt + } + (a, b) => not_supported!("Unknown cast between {a:?} and {b:?}"), + }) +} + +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); + let result = lower_to_mir(db, def, &body, &infer, body.body_expr)?; + Ok(Arc::new(result)) +} + +pub fn mir_body_recover( + _db: &dyn HirDatabase, + _cycle: &[String], + _def: &DefWithBodyId, +) -> Result<Arc<MirBody>> { + Err(MirLowerError::Loop) +} + +pub fn lower_to_mir( + db: &dyn HirDatabase, + owner: DefWithBodyId, + body: &Body, + infer: &InferenceResult, + // FIXME: root_expr should always be the body.body_expr, but since `X` in `[(); X]` doesn't have its own specific body yet, we + // 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 { ty: infer[root_expr].clone() }); + let mut binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new(); + // 1 to param_len is for params + let param_locals: Vec<LocalId> = if let DefWithBodyId::FunctionId(fid) = owner { + let substs = TyBuilder::placeholder_subst(db, fid); + let 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 (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, + binding_locals, + param_locals, + owner, + arg_count: body.params.len(), + }; + let mut ctx = MirLowerCtx { + result: mir, + db, + infer, + body, + owner, + current_loop_blocks: None, + discr_temp: None, + }; + 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/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower/as_place.rs new file mode 100644 index 000000000..fe8147dcd --- /dev/null +++ b/src/tools/rust-analyzer/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), + } + } + + pub(super) 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/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs b/src/tools/rust-analyzer/crates/hir-ty/src/mir/pretty.rs new file mode 100644 index 000000000..ffc08b7e3 --- /dev/null +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs index 118e5311e..8c48331b9 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/test_db.rs @@ -9,7 +9,7 @@ use base_db::{ salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, }; use hir_def::{db::DefDatabase, ModuleId}; -use hir_expand::db::AstDatabase; +use hir_expand::db::ExpandDatabase; use stdx::hash::{NoHashHashMap, NoHashHashSet}; use syntax::TextRange; use test_utils::extract_annotations; @@ -17,7 +17,7 @@ use test_utils::extract_annotations; #[salsa::database( base_db::SourceDatabaseExtStorage, base_db::SourceDatabaseStorage, - hir_expand::db::AstDatabaseStorage, + hir_expand::db::ExpandDatabaseStorage, hir_def::db::InternDatabaseStorage, hir_def::db::DefDatabaseStorage, crate::db::HirDatabaseStorage @@ -41,8 +41,8 @@ impl fmt::Debug for TestDB { } } -impl Upcast<dyn AstDatabase> for TestDB { - fn upcast(&self) -> &(dyn AstDatabase + 'static) { +impl Upcast<dyn ExpandDatabase> for TestDB { + fn upcast(&self) -> &(dyn ExpandDatabase + 'static) { &*self } } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs index ba5d9c241..83d31f002 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests.rs @@ -23,7 +23,7 @@ use hir_def::{ src::HasSource, AssocItemId, DefWithBodyId, HasModule, LocalModuleId, Lookup, ModuleDefId, }; -use hir_expand::{db::AstDatabase, InFile}; +use hir_expand::{db::ExpandDatabase, InFile}; use once_cell::race::OnceBool; use stdx::format_to; use syntax::{ @@ -61,22 +61,27 @@ fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> { Some(tracing::subscriber::set_default(subscriber)) } +#[track_caller] fn check_types(ra_fixture: &str) { check_impl(ra_fixture, false, true, false) } +#[track_caller] fn check_types_source_code(ra_fixture: &str) { check_impl(ra_fixture, false, true, true) } +#[track_caller] fn check_no_mismatches(ra_fixture: &str) { check_impl(ra_fixture, true, false, false) } +#[track_caller] fn check(ra_fixture: &str) { check_impl(ra_fixture, false, false, false) } +#[track_caller] fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_source: bool) { let _tracing = setup_tracing(); let (db, files) = TestDB::with_many_files(ra_fixture); @@ -158,7 +163,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } else { ty.display_test(&db).to_string() }; - assert_eq!(actual, expected); + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); } } @@ -174,7 +179,7 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } else { ty.display_test(&db).to_string() }; - assert_eq!(actual, expected); + assert_eq!(actual, expected, "type annotation differs at {:#?}", range.range); } if let Some(expected) = adjustments.remove(&range) { let adjustments = inference_result @@ -191,30 +196,11 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour } } - for (pat, mismatch) in inference_result.pat_type_mismatches() { - let node = match pat_node(&body_source_map, pat, &db) { - Some(value) => value, - None => continue, - }; - let range = node.as_ref().original_file_range(&db); - let actual = format!( - "expected {}, got {}", - mismatch.expected.display_test(&db), - mismatch.actual.display_test(&db) - ); - match mismatches.remove(&range) { - Some(annotation) => assert_eq!(actual, annotation), - None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual), - } - } - for (expr, mismatch) in inference_result.expr_type_mismatches() { - let node = match body_source_map.expr_syntax(expr) { - Ok(sp) => { - let root = db.parse_or_expand(sp.file_id).unwrap(); - sp.map(|ptr| ptr.to_node(&root).syntax().clone()) - } - Err(SyntheticSyntax) => continue, - }; + for (expr_or_pat, mismatch) in inference_result.type_mismatches() { + let Some(node) = (match expr_or_pat { + hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db), + hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db), + }) else { continue; }; let range = node.as_ref().original_file_range(&db); let actual = format!( "expected {}, got {}", diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs index 3e110abaf..b524922b6 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/coercion.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/hir-ty/src/tests/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/diagnostics.rs index f00fa9729..1876be303 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/diagnostics.rs @@ -73,3 +73,24 @@ fn test(x: bool) -> &'static str { "#, ); } + +#[test] +fn non_unit_block_expr_stmt_no_semi() { + check( + r#" +fn test(x: bool) { + if x { + "notok" + //^^^^^^^ expected (), got &str + } else { + "ok" + //^^^^ expected (), got &str + } + match x { true => true, false => 0 } + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected (), got bool + //^ expected bool, got i32 + () +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs index 41c53701d..378d47833 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/method_resolution.rs @@ -9,6 +9,7 @@ fn infer_slice_method() { check_types( r#" impl<T> [T] { + #[rustc_allow_incoherent_impl] fn foo(&self) -> T { loop {} } @@ -35,6 +36,7 @@ fn test() { //- /lib.rs crate:other_crate mod foo { impl f32 { + #[rustc_allow_incoherent_impl] pub fn foo(self) -> f32 { 0. } } } @@ -47,6 +49,7 @@ fn infer_array_inherent_impl() { check_types( r#" impl<T, const N: usize> [T; N] { + #[rustc_allow_incoherent_impl] fn foo(&self) -> T { loop {} } @@ -1167,7 +1170,6 @@ fn test() { 123..167 '{ ...o(); }': () 133..134 's': &S 137..151 'unsafe { f() }': &S - 137..151 'unsafe { f() }': &S 146..147 'f': fn f() -> &S 146..149 'f()': &S 157..158 's': &S @@ -1253,6 +1255,7 @@ fn foo<T: Trait>(a: &T) { #[test] fn autoderef_visibility_field() { + // FIXME: We should know mutability in overloaded deref check( r#" //- minicore: deref @@ -1274,7 +1277,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 } } @@ -1437,6 +1440,7 @@ fn resolve_const_generic_array_methods() { r#" #[lang = "array"] impl<T, const N: usize> [T; N] { + #[rustc_allow_incoherent_impl] pub fn map<F, U>(self, f: F) -> [U; N] where F: FnMut(T) -> U, @@ -1445,6 +1449,7 @@ impl<T, const N: usize> [T; N] { #[lang = "slice"] impl<T> [T] { + #[rustc_allow_incoherent_impl] pub fn map<F, U>(self, f: F) -> &[U] where F: FnMut(T) -> U, @@ -1468,6 +1473,7 @@ struct Const<const N: usize>; #[lang = "array"] impl<T, const N: usize> [T; N] { + #[rustc_allow_incoherent_impl] pub fn my_map<F, U, const X: usize>(self, f: F, c: Const<X>) -> [U; X] where F: FnMut(T) -> U, @@ -1476,6 +1482,7 @@ impl<T, const N: usize> [T; N] { #[lang = "slice"] impl<T> [T] { + #[rustc_allow_incoherent_impl] pub fn my_map<F, const X: usize, U>(self, f: F, c: Const<X>) -> &[U] where F: FnMut(T) -> U, @@ -1874,14 +1881,14 @@ fn incoherent_impls() { pub struct Box<T>(T); use core::error::Error; -#[rustc_allow_incoherent_impl] impl dyn Error { + #[rustc_allow_incoherent_impl] pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<dyn Error>> { loop {} } } -#[rustc_allow_incoherent_impl] impl dyn Error + Send { + #[rustc_allow_incoherent_impl] /// Attempts to downcast the box to a concrete type. pub fn downcast<T: Error + 'static>(self: Box<Self>) -> Result<Box<T>, Box<dyn Error + Send>> { let err: Box<dyn Error> = self; diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs index 9333e2693..74bcab6ca 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/patterns.rs @@ -476,7 +476,7 @@ fn infer_adt_pattern() { 183..184 'x': usize 190..191 'x': usize 201..205 'E::B': E - 209..212 'foo': bool + 209..212 'foo': {unknown} 216..217 '1': usize 227..231 'E::B': E 235..237 '10': usize @@ -953,9 +953,9 @@ fn main() { 42..51 'true | ()': bool 49..51 '()': () 57..59 '{}': () - 68..80 '(() | true,)': ((),) + 68..80 '(() | true,)': (bool,) 69..71 '()': () - 69..78 '() | true': () + 69..78 '() | true': bool 74..78 'true': bool 74..78 'true': bool 84..86 '{}': () @@ -964,19 +964,15 @@ fn main() { 96..102 '_ | ()': bool 100..102 '()': () 108..110 '{}': () - 119..128 '(() | _,)': ((),) + 119..128 '(() | _,)': (bool,) 120..122 '()': () - 120..126 '() | _': () + 120..126 '() | _': bool 125..126 '_': bool 132..134 '{}': () 49..51: expected bool, got () - 68..80: expected (bool,), got ((),) 69..71: expected bool, got () - 69..78: expected bool, got () 100..102: expected bool, got () - 119..128: expected (bool,), got ((),) 120..122: expected bool, got () - 120..126: expected bool, got () "#]], ); } @@ -1092,3 +1088,19 @@ fn my_fn(foo: ...) {} "#, ); } + +#[test] +fn ref_pat_mutability() { + check( + r#" +fn foo() { + let &() = &(); + let &mut () = &mut (); + let &mut () = &(); + //^^^^^^^ expected &(), got &mut () + let &() = &mut (); + //^^^ expected &mut (), got &() +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs index de6ae7fff..689f0da44 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/regression.rs @@ -270,7 +270,7 @@ fn infer_std_crash_5() { 61..320 '{ ... }': () 75..79 'name': &{unknown} 82..166 'if doe... }': &{unknown} - 85..98 'doesnt_matter': bool + 85..98 'doesnt_matter': {unknown} 99..128 '{ ... }': &{unknown} 113..118 'first': &{unknown} 134..166 '{ ... }': &{unknown} @@ -279,7 +279,7 @@ fn infer_std_crash_5() { 181..188 'content': &{unknown} 191..313 'if ICE... }': &{unknown} 194..231 'ICE_RE..._VALUE': {unknown} - 194..247 'ICE_RE...&name)': bool + 194..247 'ICE_RE...&name)': {unknown} 241..246 '&name': &&{unknown} 242..246 'name': &{unknown} 248..276 '{ ... }': &{unknown} @@ -1015,9 +1015,9 @@ fn cfg_tail() { 20..31 '{ "first" }': () 22..29 '"first"': &str 72..190 '{ ...] 13 }': () - 78..88 '{ "fake" }': &str + 78..88 '{ "fake" }': () 80..86 '"fake"': &str - 93..103 '{ "fake" }': &str + 93..103 '{ "fake" }': () 95..101 '"fake"': &str 108..120 '{ "second" }': () 110..118 '"second"': &str @@ -1744,3 +1744,47 @@ fn foo(b: Bar) { "#, ); } + +#[test] +fn regression_14305() { + check_no_mismatches( + r#" +//- minicore: add +trait Tr {} +impl Tr for [u8; C] {} +const C: usize = 2 + 2; +"#, + ); +} + +#[test] +fn regression_14164() { + check_types( + r#" +trait Rec { + type K; + type Rebind<Tok>: Rec<K = Tok>; +} + +trait Expr<K> { + type Part: Rec<K = K>; + fn foo(_: <Self::Part as Rec>::Rebind<i32>) {} +} + +struct Head<K>(K); +impl<K> Rec for Head<K> { + type K = K; + type Rebind<Tok> = Head<Tok>; +} + +fn test<E>() +where + E: Expr<usize, Part = Head<usize>>, +{ + let head; + //^^^^ Head<i32> + E::foo(head); +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs index 2e5787b70..13cc3fea5 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/simple.rs @@ -352,7 +352,6 @@ unsafe fn baz(u: MyUnion) { 71..89 'MyUnio...o: 0 }': MyUnion 86..87 '0': u32 95..113 'unsafe...(u); }': () - 95..113 'unsafe...(u); }': () 104..107 'baz': fn baz(MyUnion) 104..110 'baz(u)': () 108..109 'u': MyUnion @@ -360,7 +359,6 @@ unsafe fn baz(u: MyUnion) { 126..146 'MyUnio... 0.0 }': MyUnion 141..144 '0.0': f32 152..170 'unsafe...(u); }': () - 152..170 'unsafe...(u); }': () 161..164 'baz': fn baz(MyUnion) 161..167 'baz(u)': () 165..166 'u': MyUnion @@ -1118,21 +1116,22 @@ fn infer_inherent_method() { fn infer_inherent_method_str() { check_infer( r#" - #[lang = "str"] - impl str { - fn foo(&self) -> i32 {} - } +#![rustc_coherence_is_core] +#[lang = "str"] +impl str { + fn foo(&self) -> i32 {} +} - fn test() { - "foo".foo(); - } - "#, +fn test() { + "foo".foo(); +} +"#, expect![[r#" - 39..43 'self': &str - 52..54 '{}': i32 - 68..88 '{ ...o(); }': () - 74..79 '"foo"': &str - 74..85 '"foo".foo()': i32 + 67..71 'self': &str + 80..82 '{}': i32 + 96..116 '{ ...o(); }': () + 102..107 '"foo"': &str + 102..113 '"foo".foo()': i32 "#]], ); } @@ -2077,22 +2076,17 @@ async fn main() { 16..193 '{ ...2 }; }': () 26..27 'x': i32 30..43 'unsafe { 92 }': i32 - 30..43 'unsafe { 92 }': i32 39..41 '92': i32 53..54 'y': impl Future<Output = ()> - 57..85 'async ...wait }': () 57..85 'async ...wait }': impl Future<Output = ()> - 65..77 'async { () }': () 65..77 'async { () }': impl Future<Output = ()> 65..83 'async ....await': () 73..75 '()': () 95..96 'z': ControlFlow<(), ()> - 130..140 'try { () }': () 130..140 'try { () }': ControlFlow<(), ()> 136..138 '()': () 150..151 'w': i32 154..166 'const { 92 }': i32 - 154..166 'const { 92 }': i32 162..164 '92': i32 176..177 't': i32 180..190 ''a: { 92 }': i32 @@ -2122,7 +2116,6 @@ fn main() { 83..84 'f': F 89..91 '{}': () 103..231 '{ ... }); }': () - 109..161 'async ... }': Result<(), ()> 109..161 'async ... }': impl Future<Output = Result<(), ()>> 125..139 'return Err(())': ! 132..135 'Err': Err<(), ()>(()) -> Result<(), ()> @@ -2134,7 +2127,6 @@ fn main() { 167..171 'test': fn test<(), (), || -> impl Future<Output = Result<(), ()>>, impl Future<Output = Result<(), ()>>>(|| -> impl Future<Output = Result<(), ()>>) 167..228 'test(|... })': () 172..227 '|| asy... }': || -> impl Future<Output = Result<(), ()>> - 175..227 'async ... }': Result<(), ()> 175..227 'async ... }': impl Future<Output = Result<(), ()>> 191..205 'return Err(())': ! 198..201 'Err': Err<(), ()>(()) -> Result<(), ()> @@ -2649,6 +2641,7 @@ impl<T> [T] {} #[lang = "slice_alloc"] impl<T> [T] { + #[rustc_allow_incoherent_impl] pub fn into_vec<A: Allocator>(self: Box<Self, A>) -> Vec<T, A> { unimplemented!() } @@ -2664,22 +2657,22 @@ struct Astruct; impl B for Astruct {} "#, expect![[r#" - 569..573 'self': Box<[T], A> - 602..634 '{ ... }': Vec<T, A> - 648..761 '{ ...t]); }': () - 658..661 'vec': Vec<i32, Global> - 664..679 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global> - 664..691 '<[_]>:...1i32])': Vec<i32, Global> - 680..690 'box [1i32]': Box<[i32; 1], Global> - 684..690 '[1i32]': [i32; 1] - 685..689 '1i32': i32 - 701..702 'v': Vec<Box<dyn B, Global>, Global> - 722..739 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global> - 722..758 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global> - 740..757 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global> - 744..757 '[box Astruct]': [Box<dyn B, Global>; 1] - 745..756 'box Astruct': Box<Astruct, Global> - 749..756 'Astruct': Astruct + 604..608 'self': Box<[T], A> + 637..669 '{ ... }': Vec<T, A> + 683..796 '{ ...t]); }': () + 693..696 'vec': Vec<i32, Global> + 699..714 '<[_]>::into_vec': fn into_vec<i32, Global>(Box<[i32], Global>) -> Vec<i32, Global> + 699..726 '<[_]>:...1i32])': Vec<i32, Global> + 715..725 'box [1i32]': Box<[i32; 1], Global> + 719..725 '[1i32]': [i32; 1] + 720..724 '1i32': i32 + 736..737 'v': Vec<Box<dyn B, Global>, Global> + 757..774 '<[_]> ...to_vec': fn into_vec<Box<dyn B, Global>, Global>(Box<[Box<dyn B, Global>], Global>) -> Vec<Box<dyn B, Global>, Global> + 757..793 '<[_]> ...ruct])': Vec<Box<dyn B, Global>, Global> + 775..792 'box [b...truct]': Box<[Box<dyn B, Global>; 1], Global> + 779..792 '[box Astruct]': [Box<dyn B, Global>; 1] + 780..791 'box Astruct': Box<Astruct, Global> + 784..791 'Astruct': Astruct "#]], ) } @@ -3283,3 +3276,18 @@ fn func() { "#]], ); } + +#[test] +fn issue_14275() { + // FIXME: evaluate const generic + check_types( + r#" +struct Foo<const T: bool>; +fn main() { + const B: bool = false; + let foo = Foo::<B>; + //^^^ Foo<_> +} +"#, + ); +} diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs index 015085bde..da76d7fd8 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/tests/traits.rs @@ -83,6 +83,46 @@ async fn test() { } #[test] +fn infer_async_closure() { + check_types( + r#" +//- minicore: future, option +async fn test() { + let f = async move |x: i32| x + 42; + f; +// ^ |i32| -> impl Future<Output = i32> + let a = f(4); + a; +// ^ impl Future<Output = i32> + let x = a.await; + x; +// ^ i32 + let f = async move || 42; + f; +// ^ || -> impl Future<Output = i32> + let a = f(); + a; +// ^ impl Future<Output = i32> + let x = a.await; + x; +// ^ i32 + let b = ((async move || {})()).await; + b; +// ^ () + let c = async move || { + let y = None; + y + // ^ Option<u64> + }; + let _: Option<u64> = c().await; + c; +// ^ || -> impl Future<Output = Option<u64>> +} +"#, + ); +} + +#[test] fn auto_sized_async_block() { check_no_mismatches( r#" @@ -493,29 +533,30 @@ fn tuple_struct_with_fn() { r#" struct S(fn(u32) -> u64); fn test() -> u64 { - let a = S(|i| 2*i); + let a = S(|i| 2*i as u64); let b = a.0(4); a.0(2) }"#, expect![[r#" - 43..101 '{ ...0(2) }': u64 + 43..108 '{ ...0(2) }': u64 53..54 'a': S 57..58 'S': S(fn(u32) -> u64) -> S - 57..67 'S(|i| 2*i)': S - 59..66 '|i| 2*i': |u32| -> u64 + 57..74 'S(|i| ...s u64)': S + 59..73 '|i| 2*i as u64': |u32| -> u64 60..61 'i': u32 - 63..64 '2': u32 - 63..66 '2*i': u32 + 63..64 '2': u64 + 63..73 '2*i as u64': u64 65..66 'i': u32 - 77..78 'b': u64 - 81..82 'a': S - 81..84 'a.0': fn(u32) -> u64 - 81..87 'a.0(4)': u64 - 85..86 '4': u32 - 93..94 'a': S - 93..96 'a.0': fn(u32) -> u64 - 93..99 'a.0(2)': u64 - 97..98 '2': u32 + 65..73 'i as u64': u64 + 84..85 'b': u64 + 88..89 'a': S + 88..91 'a.0': fn(u32) -> u64 + 88..94 'a.0(4)': u64 + 92..93 '4': u32 + 100..101 'a': S + 100..103 'a.0': fn(u32) -> u64 + 100..106 'a.0(2)': u64 + 104..105 '2': u32 "#]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs index 70d2d5efa..34d957e26 100644 --- a/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs +++ b/src/tools/rust-analyzer/crates/hir-ty/src/utils.rs @@ -5,6 +5,7 @@ use std::iter; use base_db::CrateId; use chalk_ir::{cast::Cast, fold::Shift, BoundVar, DebruijnIndex}; +use either::Either; use hir_def::{ db::DefDatabase, generics::{ @@ -19,7 +20,6 @@ use hir_def::{ }; use hir_expand::name::Name; use intern::Interned; -use itertools::Either; use rustc_hash::FxHashSet; use smallvec::{smallvec, SmallVec}; @@ -315,7 +315,10 @@ fn parent_generic_def(db: &dyn DefDatabase, def: GenericDefId) -> Option<Generic GenericDefId::TypeAliasId(it) => it.lookup(db).container, GenericDefId::ConstId(it) => it.lookup(db).container, GenericDefId::EnumVariantId(it) => return Some(it.parent.into()), - GenericDefId::AdtId(_) | GenericDefId::TraitId(_) | GenericDefId::ImplId(_) => return None, + GenericDefId::AdtId(_) + | GenericDefId::TraitId(_) + | GenericDefId::ImplId(_) + | GenericDefId::TraitAliasId(_) => return None, }; match container { diff --git a/src/tools/rust-analyzer/crates/hir/src/attrs.rs b/src/tools/rust-analyzer/crates/hir/src/attrs.rs index 54425d69b..db0b84ef0 100644 --- a/src/tools/rust-analyzer/crates/hir/src/attrs.rs +++ b/src/tools/rust-analyzer/crates/hir/src/attrs.rs @@ -14,7 +14,8 @@ use syntax::{ast, AstNode}; use crate::{ Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, - Macro, Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant, + Macro, Module, ModuleDef, Static, Struct, Trait, TraitAlias, TypeAlias, TypeParam, Union, + Variant, }; pub trait HasAttrs { @@ -60,6 +61,7 @@ impl_has_attrs![ (Static, StaticId), (Const, ConstId), (Trait, TraitId), + (TraitAlias, TraitAliasId), (TypeAlias, TypeAliasId), (Macro, MacroId), (Function, FunctionId), @@ -134,6 +136,7 @@ fn resolve_doc_path( AttrDefId::StaticId(it) => it.resolver(db.upcast()), AttrDefId::ConstId(it) => it.resolver(db.upcast()), AttrDefId::TraitId(it) => it.resolver(db.upcast()), + AttrDefId::TraitAliasId(it) => it.resolver(db.upcast()), AttrDefId::TypeAliasId(it) => it.resolver(db.upcast()), AttrDefId::ImplId(it) => it.resolver(db.upcast()), AttrDefId::ExternBlockId(it) => it.resolver(db.upcast()), diff --git a/src/tools/rust-analyzer/crates/hir/src/db.rs b/src/tools/rust-analyzer/crates/hir/src/db.rs index e25d86784..0935b5ea5 100644 --- a/src/tools/rust-analyzer/crates/hir/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir/src/db.rs @@ -5,8 +5,9 @@ //! But we need this for at least LRU caching at the query level. pub use hir_def::db::*; pub use hir_expand::db::{ - AstDatabase, AstDatabaseStorage, AstIdMapQuery, HygieneFrameQuery, InternMacroCallQuery, - MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery, + AstIdMapQuery, ExpandDatabase, ExpandDatabaseStorage, ExpandProcMacroQuery, HygieneFrameQuery, + InternMacroCallQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandErrorQuery, + MacroExpandQuery, ParseMacroExpansionQuery, }; pub use hir_ty::db::*; diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 54d43fa8d..253d62daf 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -3,6 +3,8 @@ //! //! This probably isn't the best way to do this -- ideally, diagnostics should //! be expressed in terms of hir types themselves. +pub use hir_ty::diagnostics::{IncoherentImpl, IncorrectCase}; + use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; @@ -10,7 +12,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,)*) => { @@ -31,15 +33,18 @@ macro_rules! diagnostics { diagnostics![ BreakOutsideOfLoop, + ExpectedFunction, InactiveCode, IncorrectCase, InvalidDeriveTarget, + IncoherentImpl, MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, + NeedMut, NoSuchField, PrivateAssocItem, PrivateField, @@ -47,10 +52,13 @@ diagnostics![ TypeMismatch, UnimplementedBuiltinMacro, UnresolvedExternCrate, + UnresolvedField, UnresolvedImport, UnresolvedMacroCall, + UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, + UnusedMut, ]; #[derive(Debug)] @@ -131,6 +139,28 @@ pub struct PrivateAssocItem { } #[derive(Debug)] +pub struct ExpectedFunction { + pub call: InFile<AstPtr<ast::Expr>>, + pub found: Type, +} + +#[derive(Debug)] +pub struct UnresolvedField { + pub expr: InFile<AstPtr<ast::Expr>>, + pub receiver: Type, + pub name: Name, + pub method_with_same_name_exists: bool, +} + +#[derive(Debug)] +pub struct UnresolvedMethodCall { + pub expr: InFile<AstPtr<ast::Expr>>, + pub receiver: Type, + pub name: Name, + pub field_with_same_name: Option<Type>, +} + +#[derive(Debug)] pub struct PrivateField { pub expr: InFile<AstPtr<ast::Expr>>, pub field: Field, @@ -140,6 +170,7 @@ pub struct PrivateField { pub struct BreakOutsideOfLoop { pub expr: InFile<AstPtr<ast::Expr>>, pub is_break: bool, + pub bad_value_break: bool, } #[derive(Debug)] @@ -171,17 +202,24 @@ pub struct MismatchedArgCount { #[derive(Debug)] pub struct MissingMatchArms { - pub file: HirFileId, - pub match_expr: AstPtr<ast::Expr>, + pub scrutinee_expr: InFile<AstPtr<ast::Expr>>, pub uncovered_patterns: String, } #[derive(Debug)] pub struct TypeMismatch { - // FIXME: add mismatches in patterns as well - pub expr: InFile<AstPtr<ast::Expr>>, + pub expr_or_pat: Either<InFile<AstPtr<ast::Expr>>, InFile<AstPtr<ast::Pat>>>, pub expected: Type, pub actual: Type, } -pub use hir_ty::diagnostics::IncorrectCase; +#[derive(Debug)] +pub struct NeedMut { + pub local: Local, + pub span: InFile<SyntaxNodePtr>, +} + +#[derive(Debug)] +pub struct UnusedMut { + pub local: Local, +} diff --git a/src/tools/rust-analyzer/crates/hir/src/display.rs b/src/tools/rust-analyzer/crates/hir/src/display.rs index 0d1942012..5aae92efd 100644 --- a/src/tools/rust-analyzer/crates/hir/src/display.rs +++ b/src/tools/rust-analyzer/crates/hir/src/display.rs @@ -17,15 +17,23 @@ use hir_ty::{ }; use crate::{ - Adt, Const, ConstParam, Enum, Field, Function, GenericParam, HasCrate, HasVisibility, - LifetimeParam, Macro, Module, Static, Struct, Trait, TyBuilder, Type, TypeAlias, - TypeOrConstParam, TypeParam, Union, Variant, + Adt, AsAssocItem, AssocItemContainer, Const, ConstParam, Enum, Field, Function, GenericParam, + HasCrate, HasVisibility, LifetimeParam, Macro, Module, Static, Struct, Trait, TraitAlias, + TyBuilder, Type, TypeAlias, TypeOrConstParam, TypeParam, Union, Variant, }; impl HirDisplay for Function { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - let data = f.db.function_data(self.id); - write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + let db = f.db; + let data = db.function_data(self.id); + let container = self.as_assoc_item(db).map(|it| it.container(db)); + let mut module = self.module(db); + if let Some(AssocItemContainer::Impl(_)) = container { + // Block-local impls are "hoisted" to the nearest (non-block) module. + module = module.nearest_non_block_module(db); + } + let module_id = module.id; + write_visibility(module_id, self.visibility(db), f)?; if data.has_default_kw() { f.write_str("default ")?; } @@ -35,7 +43,7 @@ impl HirDisplay for Function { if data.has_async_kw() { f.write_str("async ")?; } - if self.is_unsafe_to_call(f.db) { + if self.is_unsafe_to_call(db) { f.write_str("unsafe ")?; } if let Some(abi) = &data.abi { @@ -50,7 +58,7 @@ impl HirDisplay for Function { let write_self_param = |ty: &TypeRef, f: &mut HirFormatter<'_>| match ty { TypeRef::Path(p) if p.is_self_type() => f.write_str("self"), - TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner,TypeRef::Path(p) if p.is_self_type()) => + TypeRef::Reference(inner, lifetime, mut_) if matches!(&**inner, TypeRef::Path(p) if p.is_self_type()) => { f.write_char('&')?; if let Some(lifetime) = lifetime { @@ -442,8 +450,15 @@ fn write_where_clause(def: GenericDefId, f: &mut HirFormatter<'_>) -> Result<(), impl HirDisplay for Const { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { - write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; - let data = f.db.const_data(self.id); + let db = f.db; + let container = self.as_assoc_item(db).map(|it| it.container(db)); + let mut module = self.module(db); + if let Some(AssocItemContainer::Impl(_)) = container { + // Block-local impls are "hoisted" to the nearest (non-block) module. + module = module.nearest_non_block_module(db); + } + write_visibility(module.id, self.visibility(db), f)?; + let data = db.const_data(self.id); f.write_str("const ")?; match &data.name { Some(name) => write!(f, "{name}: ")?, @@ -486,6 +501,22 @@ impl HirDisplay for Trait { } } +impl HirDisplay for TraitAlias { + fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { + write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; + let data = f.db.trait_alias_data(self.id); + write!(f, "trait {}", data.name)?; + let def_id = GenericDefId::TraitAliasId(self.id); + write_generic_params(def_id, f)?; + f.write_str(" = ")?; + // FIXME: Currently we lower every bounds in a trait alias as a trait bound on `Self` i.e. + // `trait Foo = Bar` is stored and displayed as `trait Foo = where Self: Bar`, which might + // be less readable. + write_where_clause(def_id, f)?; + Ok(()) + } +} + impl HirDisplay for TypeAlias { fn hir_fmt(&self, f: &mut HirFormatter<'_>) -> Result<(), HirDisplayError> { write_visibility(self.module(f.db).id, self.visibility(f.db), f)?; diff --git a/src/tools/rust-analyzer/crates/hir/src/from_id.rs b/src/tools/rust-analyzer/crates/hir/src/from_id.rs index f825a72c0..aaaa7abf3 100644 --- a/src/tools/rust-analyzer/crates/hir/src/from_id.rs +++ b/src/tools/rust-analyzer/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, }; @@ -37,6 +37,7 @@ from_id![ (hir_def::EnumId, crate::Enum), (hir_def::TypeAliasId, crate::TypeAlias), (hir_def::TraitId, crate::Trait), + (hir_def::TraitAliasId, crate::TraitAlias), (hir_def::StaticId, crate::Static), (hir_def::ConstId, crate::Const), (hir_def::FunctionId, crate::Function), @@ -110,6 +111,7 @@ impl From<ModuleDefId> for ModuleDef { ModuleDefId::ConstId(it) => ModuleDef::Const(it.into()), ModuleDefId::StaticId(it) => ModuleDef::Static(it.into()), ModuleDefId::TraitId(it) => ModuleDef::Trait(it.into()), + ModuleDefId::TraitAliasId(it) => ModuleDef::TraitAlias(it.into()), ModuleDefId::TypeAliasId(it) => ModuleDef::TypeAlias(it.into()), ModuleDefId::BuiltinType(it) => ModuleDef::BuiltinType(it.into()), ModuleDefId::MacroId(it) => ModuleDef::Macro(it.into()), @@ -127,6 +129,7 @@ impl From<ModuleDef> for ModuleDefId { ModuleDef::Const(it) => ModuleDefId::ConstId(it.into()), ModuleDef::Static(it) => ModuleDefId::StaticId(it.into()), ModuleDef::Trait(it) => ModuleDefId::TraitId(it.into()), + ModuleDef::TraitAlias(it) => ModuleDefId::TraitAliasId(it.into()), ModuleDef::TypeAlias(it) => ModuleDefId::TypeAliasId(it.into()), ModuleDef::BuiltinType(it) => ModuleDefId::BuiltinType(it.into()), ModuleDef::Macro(it) => ModuleDefId::MacroId(it.into()), @@ -172,6 +175,7 @@ impl From<GenericDef> for GenericDefId { GenericDef::Function(it) => GenericDefId::FunctionId(it.id), GenericDef::Adt(it) => GenericDefId::AdtId(it.into()), GenericDef::Trait(it) => GenericDefId::TraitId(it.id), + GenericDef::TraitAlias(it) => GenericDefId::TraitAliasId(it.id), GenericDef::TypeAlias(it) => GenericDefId::TypeAliasId(it.id), GenericDef::Impl(it) => GenericDefId::ImplId(it.id), GenericDef::Variant(it) => GenericDefId::EnumVariantId(it.into()), @@ -186,6 +190,7 @@ impl From<GenericDefId> for GenericDef { GenericDefId::FunctionId(it) => GenericDef::Function(it.into()), GenericDefId::AdtId(it) => GenericDef::Adt(it.into()), GenericDefId::TraitId(it) => GenericDef::Trait(it.into()), + GenericDefId::TraitAliasId(it) => GenericDef::TraitAlias(it.into()), GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()), GenericDefId::ImplId(it) => GenericDef::Impl(it.into()), GenericDefId::EnumVariantId(it) => GenericDef::Variant(it.into()), @@ -246,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/src/tools/rust-analyzer/crates/hir/src/has_source.rs b/src/tools/rust-analyzer/crates/hir/src/has_source.rs index f8b01db3e..9f6b5c0a9 100644 --- a/src/tools/rust-analyzer/crates/hir/src/has_source.rs +++ b/src/tools/rust-analyzer/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, 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 { @@ -122,6 +123,12 @@ impl HasSource for Trait { Some(self.id.lookup(db.upcast()).source(db.upcast())) } } +impl HasSource for TraitAlias { + type Ast = ast::TraitAlias; + fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> { + Some(self.id.lookup(db.upcast()).source(db.upcast())) + } +} impl HasSource for TypeAlias { type Ast = ast::TypeAlias; fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> { @@ -158,7 +165,7 @@ impl HasSource for Impl { } impl HasSource for TypeOrConstParam { - type Ast = Either<ast::TypeOrConstParam, ast::Trait>; + type Ast = Either<ast::TypeOrConstParam, ast::TraitOrAlias>; fn source(self, db: &dyn HirDatabase) -> Option<InFile<Self::Ast>> { let child_source = self.id.parent.child_source(db.upcast()); Some(child_source.map(|it| it[self.id.local_id].clone())) @@ -172,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/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 2cb4ed2c3..35424feec 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -41,34 +41,34 @@ use either::Either; use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, - expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{TypeOrConstParamData, TypeParamProvenance}, + expr::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat}, + generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, - nameres::{self, diagnostics::DefDiagnostic}, + nameres::{self, diagnostics::DefDiagnostic, ModuleOrigin}, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, - type_ref::ConstScalar, AdtId, AssocItemId, AssocItemLoc, AttrDefId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, ItemContainerId, LifetimeParamId, LocalEnumVariantId, LocalFieldId, Lookup, MacroExpander, MacroId, ModuleId, StaticId, StructId, - TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, + TraitAliasId, TraitId, TypeAliasId, TypeOrConstParamId, TypeParamId, UnionId, }; use hir_expand::{name::name, MacroCallKind}; use hir_ty::{ all_super_traits, autoderef, - consteval::{unknown_const_as_generic, ComputedExpr, ConstEvalError, ConstExt}, + consteval::{try_const_usize, unknown_const_as_generic, ConstEvalError, ConstExt}, diagnostics::BodyValidationDiagnostic, + display::HexifiedConst, layout::layout_of_ty, method_resolution::{self, TyFingerprint}, + mir::{self, interpret_mir}, primitive::UintTy, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, - ConcreteConst, ConstValue, GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, - Substitution, TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, - WhereClause, + GenericArgData, Interner, ParamKind, QuantifiedWhereClause, Scalar, Substitution, + TraitEnvironment, TraitRefExt, Ty, TyBuilder, TyDefId, TyExt, TyKind, WhereClause, }; use itertools::Itertools; use nameres::diagnostics::DefDiagnosticKind; @@ -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}; @@ -85,12 +85,12 @@ use crate::db::{DefDatabase, HirDatabase}; pub use crate::{ attrs::{HasAttrs, Namespace}, diagnostics::{ - AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget, - MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, - MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField, - ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, - UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, - UnresolvedProcMacro, + AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl, + IncorrectCase, InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, + MissingFields, MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, + PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro, + UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall, + UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut, }, has_source::HasSource, semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits}, @@ -130,6 +130,7 @@ pub use { }, hir_ty::{ display::{HirDisplay, HirDisplayError, HirWrite}, + mir::MirEvalError, PointerCast, Safety, }, }; @@ -272,6 +273,7 @@ pub enum ModuleDef { Const(Const), Static(Static), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), BuiltinType(BuiltinType), Macro(Macro), @@ -284,6 +286,7 @@ impl_from!( Const, Static, Trait, + TraitAlias, TypeAlias, BuiltinType, Macro @@ -310,6 +313,7 @@ impl ModuleDef { ModuleDef::Const(it) => Some(it.module(db)), ModuleDef::Static(it) => Some(it.module(db)), ModuleDef::Trait(it) => Some(it.module(db)), + ModuleDef::TraitAlias(it) => Some(it.module(db)), ModuleDef::TypeAlias(it) => Some(it.module(db)), ModuleDef::Macro(it) => Some(it.module(db)), ModuleDef::BuiltinType(_) => None, @@ -338,6 +342,7 @@ impl ModuleDef { ModuleDef::Const(it) => it.name(db)?, ModuleDef::Adt(it) => it.name(db), ModuleDef::Trait(it) => it.name(db), + ModuleDef::TraitAlias(it) => it.name(db), ModuleDef::Function(it) => it.name(db), ModuleDef::Variant(it) => it.name(db), ModuleDef::TypeAlias(it) => it.name(db), @@ -356,6 +361,7 @@ impl ModuleDef { Adt::Union(it) => it.id.into(), }, ModuleDef::Trait(it) => it.id.into(), + ModuleDef::TraitAlias(it) => it.id.into(), ModuleDef::Function(it) => it.id.into(), ModuleDef::TypeAlias(it) => it.id.into(), ModuleDef::Module(it) => it.id.into(), @@ -398,6 +404,7 @@ impl ModuleDef { ModuleDef::Module(_) | ModuleDef::Adt(_) | ModuleDef::Trait(_) + | ModuleDef::TraitAlias(_) | ModuleDef::TypeAlias(_) | ModuleDef::Macro(_) | ModuleDef::BuiltinType(_) => None, @@ -413,6 +420,7 @@ impl ModuleDef { ModuleDef::Const(it) => it.attrs(db), ModuleDef::Static(it) => it.attrs(db), ModuleDef::Trait(it) => it.attrs(db), + ModuleDef::TraitAlias(it) => it.attrs(db), ModuleDef::TypeAlias(it) => it.attrs(db), ModuleDef::Macro(it) => it.attrs(db), ModuleDef::BuiltinType(_) => return None, @@ -429,6 +437,7 @@ impl HasVisibility for ModuleDef { ModuleDef::Const(it) => it.visibility(db), ModuleDef::Static(it) => it.visibility(db), ModuleDef::Trait(it) => it.visibility(db), + ModuleDef::TraitAlias(it) => it.visibility(db), ModuleDef::TypeAlias(it) => it.visibility(db), ModuleDef::Variant(it) => it.visibility(db), ModuleDef::Macro(it) => it.visibility(db), @@ -488,6 +497,20 @@ impl Module { Some(Module { id: def_map.module_id(parent_id) }) } + /// Finds nearest non-block ancestor `Module` (`self` included). + pub fn nearest_non_block_module(self, db: &dyn HirDatabase) -> Module { + let mut id = self.id; + loop { + let def_map = id.def_map(db.upcast()); + let origin = def_map[id.local_id].origin; + if matches!(origin, ModuleOrigin::BlockExpr { .. }) { + id = id.containing_module(db.upcast()).expect("block without parent module") + } else { + return Module { id }; + } + } + } + pub fn path_to_root(self, db: &dyn HirDatabase) -> Vec<Module> { let mut res = vec![self]; let mut curr = self; @@ -581,11 +604,23 @@ impl Module { } } + let inherent_impls = db.inherent_impls_in_crate(self.id.krate()); + for impl_def in self.impl_defs(db) { for diag in db.impl_data_with_diagnostics(impl_def.id).1.iter() { emit_def_diagnostic(db, acc, diag); } + if inherent_impls.invalid_impls().contains(&impl_def.id) { + let loc = impl_def.id.lookup(db.upcast()); + let tree = loc.id.item_tree(db.upcast()); + let node = &tree[loc.id.value]; + let file_id = loc.id.file_id(); + let ast_id_map = db.ast_id_map(file_id); + + acc.push(IncoherentImpl { impl_: ast_id_map.get(node.ast_id()), file_id }.into()) + } + for item in impl_def.items(db) { let def: DefWithBody = match item { AssocItem::Function(it) => it.into(), @@ -1092,8 +1127,8 @@ impl Variant { self.source(db)?.value.expr() } - pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> { - db.const_eval_variant(self.into()) + pub fn eval(self, db: &dyn HirDatabase) -> Result<i128, ConstEvalError> { + db.const_eval_discriminant(self.into()) } } @@ -1170,6 +1205,25 @@ impl Adt { } } + /// Returns the lifetime of the DataType + pub fn lifetime(&self, db: &dyn HirDatabase) -> Option<LifetimeParamData> { + let resolver = match self { + Adt::Struct(s) => s.id.resolver(db.upcast()), + Adt::Union(u) => u.id.resolver(db.upcast()), + Adt::Enum(e) => e.id.resolver(db.upcast()), + }; + resolver + .generic_params() + .and_then(|gp| { + (&gp.lifetimes) + .iter() + // there should only be a single lifetime + // but `Arena` requires to use an iterator + .nth(0) + }) + .map(|arena| arena.1.clone()) + } + pub fn as_enum(&self) -> Option<Enum> { if let Self::Enum(v) = self { Some(*v) @@ -1285,6 +1339,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(); @@ -1334,42 +1397,35 @@ impl DefWithBody { let infer = db.infer(self.into()); let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1); + let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic"); for d in &infer.diagnostics { match d { - hir_ty::InferenceDiagnostic::NoSuchField { expr } => { - let field = source_map.field_syntax(*expr); + &hir_ty::InferenceDiagnostic::NoSuchField { expr } => { + let field = source_map.field_syntax(expr); acc.push(NoSuchField { field }.into()) } - &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { expr, is_break } => { - let expr = source_map - .expr_syntax(expr) - .expect("break outside of loop in synthetic syntax"); - acc.push(BreakOutsideOfLoop { expr, is_break }.into()) + &hir_ty::InferenceDiagnostic::BreakOutsideOfLoop { + expr, + is_break, + bad_value_break, + } => { + let expr = expr_syntax(expr); + acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into()) } - hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { - match source_map.expr_syntax(*call_expr) { - Ok(source_ptr) => acc.push( - MismatchedArgCount { - call_expr: source_ptr, - expected: *expected, - found: *found, - } + &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => { + acc.push( + MismatchedArgCount { call_expr: expr_syntax(call_expr), expected, found } .into(), - ), - Err(SyntheticSyntax) => (), - } + ) } &hir_ty::InferenceDiagnostic::PrivateField { expr, field } => { - let expr = source_map.expr_syntax(expr).expect("unexpected synthetic"); + let expr = expr_syntax(expr); let field = field.into(); acc.push(PrivateField { expr, field }.into()) } &hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => { let expr_or_pat = match id { - ExprOrPatId::ExprId(expr) => source_map - .expr_syntax(expr) - .expect("unexpected synthetic") - .map(Either::Left), + ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left), ExprOrPatId::PatId(pat) => source_map .pat_syntax(pat) .expect("unexpected synthetic") @@ -1378,16 +1434,76 @@ impl DefWithBody { let item = item.into(); acc.push(PrivateAssocItem { expr_or_pat, item }.into()) } + hir_ty::InferenceDiagnostic::ExpectedFunction { call_expr, found } => { + let call_expr = expr_syntax(*call_expr); + + acc.push( + ExpectedFunction { + call: call_expr, + found: Type::new(db, DefWithBodyId::from(self), found.clone()), + } + .into(), + ) + } + hir_ty::InferenceDiagnostic::UnresolvedField { + expr, + receiver, + name, + method_with_same_name_exists, + } => { + let expr = expr_syntax(*expr); + + acc.push( + UnresolvedField { + expr, + name: name.clone(), + receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()), + method_with_same_name_exists: *method_with_same_name_exists, + } + .into(), + ) + } + hir_ty::InferenceDiagnostic::UnresolvedMethodCall { + expr, + receiver, + name, + field_with_same_name, + } => { + let expr = expr_syntax(*expr); + + acc.push( + UnresolvedMethodCall { + expr, + name: name.clone(), + receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()), + field_with_same_name: field_with_same_name + .clone() + .map(|ty| Type::new(db, DefWithBodyId::from(self), ty)), + } + .into(), + ) + } } } - for (expr, mismatch) in infer.expr_type_mismatches() { - let expr = match source_map.expr_syntax(expr) { - Ok(expr) => expr, - Err(SyntheticSyntax) => continue, + for (pat_or_expr, mismatch) in infer.type_mismatches() { + let expr_or_pat = match pat_or_expr { + ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left), + // FIXME: Re-enable these once we have less false positives + ExprOrPatId::PatId(_pat) => continue, + #[allow(unreachable_patterns)] + ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right), + }; + let expr_or_pat = match expr_or_pat { + Ok(Either::Left(expr)) => Either::Left(expr), + Ok(Either::Right(InFile { file_id, value: Either::Left(pat) })) => { + Either::Right(InFile { file_id, value: pat }) + } + Ok(Either::Right(_)) | Err(SyntheticSyntax) => continue, }; + acc.push( TypeMismatch { - expr, + expr_or_pat, expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.clone()), actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.clone()), } @@ -1405,6 +1521,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 { @@ -1489,11 +1640,13 @@ impl DefWithBody { if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) { - if let Some(match_expr) = match_expr.expr() { + if let Some(scrut_expr) = match_expr.expr() { acc.push( MissingMatchArms { - file: source_ptr.file_id, - match_expr: AstPtr::new(&match_expr), + scrutinee_expr: InFile::new( + source_ptr.file_id, + AstPtr::new(&scrut_expr), + ), uncovered_patterns, } .into(), @@ -1582,6 +1735,10 @@ impl Function { .collect() } + pub fn num_params(self, db: &dyn HirDatabase) -> usize { + db.function_data(self.id).params.len() + } + pub fn method_params(self, db: &dyn HirDatabase) -> Option<Vec<Param>> { if self.self_param(db).is_none() { return None; @@ -1639,6 +1796,14 @@ impl Function { let def_map = db.crate_def_map(loc.krate(db).into()); def_map.fn_as_proc_macro(self.id).map(|id| Macro { id: id.into() }) } + + pub fn eval(self, db: &dyn HirDatabase) -> Result<(), MirEvalError> { + let body = db + .mir_body(self.id.into()) + .map_err(|e| MirEvalError::MirLowerError(self.id.into(), e))?; + interpret_mir(db, &body, false)?; + Ok(()) + } } // Note: logically, this belongs to `hir_ty`, but we are not using it there yet. @@ -1679,8 +1844,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 } @@ -1781,8 +1946,18 @@ impl Const { Type::new_with_resolver_inner(db, &resolver, ty) } - pub fn eval(self, db: &dyn HirDatabase) -> Result<ComputedExpr, ConstEvalError> { - db.const_eval(self.id) + pub fn render_eval(self, db: &dyn HirDatabase) -> Result<String, ConstEvalError> { + let c = db.const_eval(self.id)?; + let r = format!("{}", HexifiedConst(c).display(db)); + // We want to see things like `<utf8-error>` and `<layout-error>` as they are probably bug in our + // implementation, but there is no need to show things like `<enum-not-supported>` or `<ref-not-supported>` to + // the user. + if r.contains("not-supported>") { + return Err(ConstEvalError::MirEvalError(MirEvalError::NotSupported( + "rendering complex constants".to_string(), + ))); + } + return Ok(r); } } @@ -1894,6 +2069,27 @@ impl HasVisibility for Trait { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TraitAlias { + pub(crate) id: TraitAliasId, +} + +impl TraitAlias { + pub fn module(self, db: &dyn HirDatabase) -> Module { + Module { id: self.id.lookup(db.upcast()).container } + } + + pub fn name(self, db: &dyn HirDatabase) -> Name { + db.trait_alias_data(self.id).name.clone() + } +} + +impl HasVisibility for TraitAlias { + fn visibility(&self, db: &dyn HirDatabase) -> Visibility { + db.trait_alias_data(self.id).visibility.resolve(db.upcast(), &self.id.resolver(db.upcast())) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct TypeAlias { pub(crate) id: TypeAliasId, } @@ -2265,6 +2461,7 @@ pub enum GenericDef { Function(Function), Adt(Adt), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), Impl(Impl), // enum variants cannot have generics themselves, but their parent enums @@ -2277,6 +2474,7 @@ impl_from!( Function, Adt(Struct, Enum, Union), Trait, + TraitAlias, TypeAlias, Impl, Variant, @@ -2317,20 +2515,53 @@ impl GenericDef { } /// A single local definition. -/// -/// If the definition of this is part of a "MultiLocal", that is a local that has multiple declarations due to or-patterns -/// then this only references a single one of those. -/// To retrieve the other locals you should use [`Local::associated_locals`] #[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() @@ -2350,13 +2581,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 { @@ -2365,15 +2590,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 { @@ -2387,34 +2609,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() } } @@ -3001,6 +3222,14 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Uint(UintTy::Usize))) } + pub fn is_float(&self) -> bool { + matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Float(_))) + } + + pub fn is_char(&self) -> bool { + matches!(self.ty.kind(Interner), TyKind::Scalar(Scalar::Char)) + } + pub fn is_int_or_uint(&self) -> bool { match self.ty.kind(Interner) { TyKind::Scalar(Scalar::Int(_) | Scalar::Uint(_)) => true, @@ -3015,6 +3244,13 @@ impl Type { } } + pub fn as_slice(&self) -> Option<Type> { + match &self.ty.kind(Interner) { + TyKind::Slice(ty) => Some(self.derived(ty.clone())), + _ => None, + } + } + pub fn strip_references(&self) -> Type { self.derived(self.ty.strip_references().clone()) } @@ -3190,6 +3426,14 @@ impl Type { matches!(self.ty.kind(Interner), TyKind::Raw(..)) } + pub fn remove_raw_ptr(&self) -> Option<Type> { + if let TyKind::Raw(_, ty) = self.ty.kind(Interner) { + Some(self.derived(ty.clone())) + } else { + None + } + } + pub fn contains_unknown(&self) -> bool { // FIXME: When we get rid of `ConstScalar::Unknown`, we can just look at precomputed // `TypeFlags` in `TyData`. @@ -3260,12 +3504,7 @@ impl Type { pub fn as_array(&self, _db: &dyn HirDatabase) -> Option<(Type, usize)> { if let TyKind::Array(ty, len) = &self.ty.kind(Interner) { - match len.data(Interner).value { - ConstValue::Concrete(ConcreteConst { interned: ConstScalar::UInt(len) }) => { - Some((self.derived(ty.clone()), len as usize)) - } - _ => None, - } + try_const_usize(len).map(|x| (self.derived(ty.clone()), x as usize)) } else { None } @@ -3321,6 +3560,24 @@ impl Type { } } + /// Iterates its type arguments + /// + /// It iterates the actual type arguments when concrete types are used + /// and otherwise the generic names. + /// It does not include `const` arguments. + /// + /// For code, such as: + /// ```text + /// struct Foo<T, U> + /// + /// impl<U> Foo<String, U> + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// ``` pub fn type_arguments(&self) -> impl Iterator<Item = Type> + '_ { self.ty .strip_references() @@ -3331,12 +3588,62 @@ impl Type { .map(move |ty| self.derived(ty)) } - pub fn iterate_method_candidates<T>( + /// Iterates its type and const arguments + /// + /// It iterates the actual type and const arguments when concrete types + /// are used and otherwise the generic names. + /// + /// For code, such as: + /// ```text + /// struct Foo<T, const U: usize, const X: usize> + /// + /// impl<U> Foo<String, U, 12> + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// - "12" + /// ``` + pub fn type_and_const_arguments<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator<Item = SmolStr> + 'a { + self.ty + .strip_references() + .as_adt() + .into_iter() + .flat_map(|(_, substs)| substs.iter(Interner)) + .filter_map(|arg| { + // arg can be either a `Ty` or `constant` + if let Some(ty) = arg.ty(Interner) { + Some(SmolStr::new(ty.display(db).to_string())) + } else if let Some(const_) = arg.constant(Interner) { + Some(SmolStr::new_inline(&const_.display(db).to_string())) + } else { + None + } + }) + } + + /// Combines lifetime indicators, type and constant parameters into a single `Iterator` + pub fn generic_parameters<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator<Item = SmolStr> + 'a { + // iterate the lifetime + self.as_adt() + .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) + .into_iter() + // add the type and const paramaters + .chain(self.type_and_const_arguments(db)) + } + + pub fn iterate_method_candidates_with_traits<T>( &self, db: &dyn HirDatabase, scope: &SemanticsScope<'_>, - // FIXME this can be retrieved from `scope`, except autoimport uses this - // to specify a different set, so the method needs to be split traits_in_scope: &FxHashSet<TraitId>, with_local_impls: Option<Module>, name: Option<&Name>, @@ -3364,6 +3671,24 @@ impl Type { slot } + pub fn iterate_method_candidates<T>( + &self, + db: &dyn HirDatabase, + scope: &SemanticsScope<'_>, + with_local_impls: Option<Module>, + name: Option<&Name>, + callback: impl FnMut(Function) -> Option<T>, + ) -> Option<T> { + self.iterate_method_candidates_with_traits( + db, + scope, + &scope.visible_traits().0, + with_local_impls, + name, + callback, + ) + } + fn iterate_method_candidates_dyn( &self, db: &dyn HirDatabase, @@ -3632,11 +3957,13 @@ impl Type { } } +// FIXME: Document this #[derive(Debug)] pub struct Callable { ty: Type, sig: CallableSig, callee: Callee, + /// Whether this is a method that was called with method call syntax. pub(crate) is_bound_method: bool, } @@ -3670,14 +3997,14 @@ impl Callable { Other => CallableKind::Other, } } - pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<ast::SelfParam> { + pub fn receiver_param(&self, db: &dyn HirDatabase) -> Option<(ast::SelfParam, Type)> { let func = match self.callee { Callee::Def(CallableDefId::FunctionId(it)) if self.is_bound_method => it, _ => return None, }; let src = func.lookup(db.upcast()).source(db.upcast()); let param_list = src.value.param_list()?; - param_list.self_param() + Some((param_list.self_param()?, self.ty.derived(self.sig.params()[0].clone()))) } pub fn n_params(&self) -> usize { self.sig.params().len() - if self.is_bound_method { 1 } else { 0 } @@ -3936,6 +4263,12 @@ impl HasCrate for Trait { } } +impl HasCrate for TraitAlias { + fn krate(&self, db: &dyn HirDatabase) -> Crate { + self.module(db).krate() + } +} + impl HasCrate for Static { fn krate(&self, db: &dyn HirDatabase) -> Crate { self.module(db).krate() diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index 486b7ee62..407ba6f65 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -12,10 +12,10 @@ use hir_def::{ macro_id_to_def_id, resolver::{self, HasResolver, Resolver, TypeNs}, type_ref::Mutability, - AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId, + AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId, }; use hir_expand::{ - db::AstDatabase, + db::ExpandDatabase, name::{known, AsName}, ExpansionInfo, MacroCallId, }; @@ -68,7 +68,8 @@ impl PathResolution { | ModuleDef::Function(_) | ModuleDef::Module(_) | ModuleDef::Static(_) - | ModuleDef::Trait(_), + | ModuleDef::Trait(_) + | ModuleDef::TraitAlias(_), ) => None, PathResolution::Def(ModuleDef::TypeAlias(alias)) => { Some(TypeNs::TypeAliasId((*alias).into())) @@ -365,6 +366,16 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_method_call(call).map(Function::from) } + /// Attempts to resolve this call expression as a method call falling back to resolving it as a field. + pub fn resolve_method_call_field_fallback( + &self, + call: &ast::MethodCallExpr, + ) -> Option<Either<Function, Field>> { + self.imp + .resolve_method_call_fallback(call) + .map(|it| it.map_left(Function::from).map_right(Field::from)) + } + pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<Function> { self.imp.resolve_await_to_poll(await_expr).map(Function::from) } @@ -400,7 +411,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_record_field(field) } - pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> { + pub fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { self.imp.resolve_record_pat_field(field) } @@ -527,8 +538,8 @@ impl<'db> SemanticsImpl<'db> { } fn expand_derive_as_pseudo_attr_macro(&self, attr: &ast::Attr) -> Option<SyntaxNode> { - let src = self.wrap_node_infile(attr.clone()); let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + let src = self.wrap_node_infile(attr.clone()); let call_id = self.with_ctx(|ctx| { ctx.attr_to_derive_macro_call(src.with_value(&adt), src).map(|(_, it, _)| it) })?; @@ -1092,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)) => { @@ -1145,6 +1159,13 @@ impl<'db> SemanticsImpl<'db> { self.analyze(call.syntax())?.resolve_method_call(self.db, call) } + fn resolve_method_call_fallback( + &self, + call: &ast::MethodCallExpr, + ) -> Option<Either<FunctionId, FieldId>> { + self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call) + } + fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<FunctionId> { self.analyze(await_expr.syntax())?.resolve_await_to_poll(self.db, await_expr) } @@ -1180,7 +1201,7 @@ impl<'db> SemanticsImpl<'db> { self.analyze(field.syntax())?.resolve_record_field(self.db, field) } - fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<Field> { + fn resolve_record_pat_field(&self, field: &ast::RecordPatField) -> Option<(Field, Type)> { self.analyze(field.syntax())?.resolve_record_pat_field(self.db, field) } @@ -1330,6 +1351,7 @@ impl<'db> SemanticsImpl<'db> { }) } ChildContainer::TraitId(it) => it.resolver(self.db.upcast()), + ChildContainer::TraitAliasId(it) => it.resolver(self.db.upcast()), ChildContainer::ImplId(it) => it.resolver(self.db.upcast()), ChildContainer::ModuleId(it) => it.resolver(self.db.upcast()), ChildContainer::EnumId(it) => it.resolver(self.db.upcast()), @@ -1514,7 +1536,7 @@ impl<'db> SemanticsImpl<'db> { fn macro_call_to_macro_id( ctx: &mut SourceToDefCtx<'_, '_>, - db: &dyn AstDatabase, + db: &dyn ExpandDatabase, macro_call_id: MacroCallId, ) -> Option<MacroId> { let loc = db.lookup_intern_macro_call(macro_call_id); @@ -1556,6 +1578,7 @@ to_def_impls![ (crate::Enum, ast::Enum, enum_to_def), (crate::Union, ast::Union, union_to_def), (crate::Trait, ast::Trait, trait_to_def), + (crate::TraitAlias, ast::TraitAlias, trait_alias_to_def), (crate::Impl, ast::Impl, impl_to_def), (crate::TypeAlias, ast::TypeAlias, type_alias_to_def), (crate::Const, ast::Const, const_to_def), @@ -1634,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() { @@ -1673,6 +1696,7 @@ impl<'a> SemanticsScope<'a> { } } +#[derive(Debug)] pub struct VisibleTraits(pub FxHashSet<TraitId>); impl ops::Deref for VisibleTraits { diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs index 2b5bfda1d..f6f8c9a25 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs @@ -89,16 +89,16 @@ 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, - TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, + TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; 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, @@ -159,6 +159,12 @@ impl SourceToDefCtx<'_, '_> { pub(super) fn trait_to_def(&mut self, src: InFile<ast::Trait>) -> Option<TraitId> { self.to_def(src, keys::TRAIT) } + pub(super) fn trait_alias_to_def( + &mut self, + src: InFile<ast::TraitAlias>, + ) -> Option<TraitAliasId> { + self.to_def(src, keys::TRAIT_ALIAS) + } pub(super) fn impl_to_def(&mut self, src: InFile<ast::Impl>) -> Option<ImplId> { self.to_def(src, keys::IMPL) } @@ -210,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 } @@ -225,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, @@ -353,6 +364,9 @@ impl SourceToDefCtx<'_, '_> { match item { ast::Item::Module(it) => self.module_to_def(container.with_value(it))?.into(), ast::Item::Trait(it) => self.trait_to_def(container.with_value(it))?.into(), + ast::Item::TraitAlias(it) => { + self.trait_alias_to_def(container.with_value(it))?.into() + } ast::Item::Impl(it) => self.impl_to_def(container.with_value(it))?.into(), ast::Item::Enum(it) => self.enum_to_def(container.with_value(it))?.into(), ast::Item::TypeAlias(it) => { @@ -400,6 +414,9 @@ impl SourceToDefCtx<'_, '_> { ast::Item::Struct(it) => self.struct_to_def(InFile::new(file_id, it))?.into(), ast::Item::Enum(it) => self.enum_to_def(InFile::new(file_id, it))?.into(), ast::Item::Trait(it) => self.trait_to_def(InFile::new(file_id, it))?.into(), + ast::Item::TraitAlias(it) => { + self.trait_alias_to_def(InFile::new(file_id, it))?.into() + } ast::Item::TypeAlias(it) => { self.type_alias_to_def(InFile::new(file_id, it))?.into() } @@ -435,6 +452,7 @@ pub(crate) enum ChildContainer { DefWithBodyId(DefWithBodyId), ModuleId(ModuleId), TraitId(TraitId), + TraitAliasId(TraitAliasId), ImplId(ImplId), EnumId(EnumId), VariantId(VariantId), @@ -447,6 +465,7 @@ impl_from! { DefWithBodyId, ModuleId, TraitId, + TraitAliasId, ImplId, EnumId, VariantId, @@ -462,6 +481,7 @@ impl ChildContainer { ChildContainer::DefWithBodyId(it) => it.child_by_source(db, file_id), ChildContainer::ModuleId(it) => it.child_by_source(db, file_id), ChildContainer::TraitId(it) => it.child_by_source(db, file_id), + ChildContainer::TraitAliasId(_) => DynMap::default(), ChildContainer::ImplId(it) => it.child_by_source(db, file_id), ChildContainer::EnumId(it) => it.child_by_source(db, file_id), ChildContainer::VariantId(it) => it.child_by_source(db, file_id), diff --git a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs index 3b39e9fa9..c24d196e1 100644 --- a/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs +++ b/src/tools/rust-analyzer/crates/hir/src/source_analyzer.rs @@ -10,6 +10,7 @@ use std::{ sync::Arc, }; +use either::Either; use hir_def::{ body::{ self, @@ -51,7 +52,7 @@ use syntax::{ use crate::{ db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr, BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static, - Struct, ToolModule, Trait, Type, TypeAlias, Variant, + Struct, ToolModule, Trait, TraitAlias, Type, TypeAlias, Variant, }; /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of @@ -266,6 +267,21 @@ impl SourceAnalyzer { Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs)) } + pub(crate) fn resolve_method_call_fallback( + &self, + db: &dyn HirDatabase, + call: &ast::MethodCallExpr, + ) -> Option<Either<FunctionId, FieldId>> { + let expr_id = self.expr_id(db, &call.clone().into())?; + let inference_result = self.infer.as_ref()?; + match inference_result.method_resolution(expr_id) { + Some((f_in_trait, substs)) => { + Some(Either::Left(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs))) + } + None => inference_result.field_resolution(expr_id).map(Either::Right), + } + } + pub(crate) fn resolve_await_to_poll( &self, db: &dyn HirDatabase, @@ -406,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, } @@ -425,14 +441,17 @@ impl SourceAnalyzer { &self, db: &dyn HirDatabase, field: &ast::RecordPatField, - ) -> Option<Field> { + ) -> Option<(Field, Type)> { let field_name = field.field_name()?.as_name(); let record_pat = ast::RecordPat::cast(field.syntax().parent().and_then(|p| p.parent())?)?; let pat_id = self.pat_id(&record_pat.into())?; let variant = self.infer.as_ref()?.variant_resolution_for_pat(pat_id)?; let variant_data = variant.variant_data(db.upcast()); let field = FieldId { parent: variant, local_id: variant_data.field(&field_name)? }; - Some(field.into()) + let (_, subst) = self.infer.as_ref()?.type_of_pat.get(pat_id)?.as_adt()?; + let field_ty = + db.field_types(variant).get(field.local_id)?.clone().substitute(Interner, subst); + Some((field.into(), Type::new_with_resolver(db, &self.resolver, field_ty))) } pub(crate) fn resolve_macro_call( @@ -791,7 +810,7 @@ impl SourceAnalyzer { || Arc::new(hir_ty::TraitEnvironment::empty(krate)), |d| db.trait_environment(d), ); - method_resolution::lookup_impl_method(db, env, func, substs) + method_resolution::lookup_impl_method(db, env, func, substs).0 } fn resolve_impl_const_or_trait_def( @@ -809,7 +828,7 @@ impl SourceAnalyzer { || Arc::new(hir_ty::TraitEnvironment::empty(krate)), |d| db.trait_environment(d), ); - method_resolution::lookup_impl_const(db, env, const_id, subs) + method_resolution::lookup_impl_const(db, env, const_id, subs).0 } fn lang_trait_fn( @@ -943,17 +962,17 @@ fn resolve_hir_path_( res.map(|ty_ns| (ty_ns, path.segments().first())) } None => { - let (ty, remaining) = + let (ty, remaining_idx) = resolver.resolve_path_in_type_ns(db.upcast(), path.mod_path())?; - match remaining { - Some(remaining) if remaining > 1 => { - if remaining + 1 == path.segments().len() { + match remaining_idx { + Some(remaining_idx) => { + if remaining_idx + 1 == path.segments().len() { Some((ty, path.segments().last())) } else { None } } - _ => Some((ty, path.segments().get(1))), + None => Some((ty, None)), } } }?; @@ -978,6 +997,7 @@ fn resolve_hir_path_( TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), + TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()), }; match unresolved { Some(unresolved) => resolver @@ -1001,8 +1021,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()), @@ -1065,6 +1085,7 @@ fn resolve_hir_path_qualifier( TypeNs::TypeAliasId(it) => PathResolution::Def(TypeAlias::from(it).into()), TypeNs::BuiltinType(it) => PathResolution::Def(BuiltinType::from(it).into()), TypeNs::TraitId(it) => PathResolution::Def(Trait::from(it).into()), + TypeNs::TraitAliasId(it) => PathResolution::Def(TraitAlias::from(it).into()), }) .or_else(|| { resolver diff --git a/src/tools/rust-analyzer/crates/hir/src/symbols.rs b/src/tools/rust-analyzer/crates/hir/src/symbols.rs index fd78decda..a9afa1c6f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/symbols.rs +++ b/src/tools/rust-analyzer/crates/hir/src/symbols.rs @@ -68,6 +68,7 @@ pub enum FileSymbolKind { Static, Struct, Trait, + TraitAlias, TypeAlias, Union, } @@ -153,6 +154,9 @@ impl<'a> SymbolCollector<'a> { self.push_decl(id, FileSymbolKind::Trait); self.collect_from_trait(id); } + ModuleDefId::TraitAliasId(id) => { + self.push_decl(id, FileSymbolKind::TraitAlias); + } ModuleDefId::TypeAliasId(id) => { self.push_decl_assoc(id, FileSymbolKind::TypeAlias); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs index 0057f439f..785ae3d09 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_explicit_type.rs @@ -211,10 +211,8 @@ fn main() { check_assist_not_applicable( add_explicit_type, r#" -//- minicore: option - fn main() { - let $0l = [0.0; Some(2).unwrap()]; + let $0l = [0.0; unresolved_function(5)]; } "#, ); diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs index f32ef2d59..9e1d9a702 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_iter_for_each_to_for.rs @@ -157,19 +157,12 @@ fn is_ref_and_impls_iter_method( let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; let has_wanted_method = ty - .iterate_method_candidates( - sema.db, - &scope, - &scope.visible_traits().0, - None, - Some(&wanted_method), - |func| { - if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { - return Some(()); - } - None - }, - ) + .iterate_method_candidates(sema.db, &scope, None, Some(&wanted_method), |func| { + if func.ret_type(sema.db).impls_trait(sema.db, iter_trait, &[]) { + return Some(()); + } + None + }) .is_some(); if !has_wanted_method { return None; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index 65c2479e9..7f2c01772 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -1,6 +1,6 @@ use ide_db::defs::{Definition, NameRefClass}; use syntax::{ - ast::{self, HasName}, + ast::{self, HasName, Name}, ted, AstNode, SyntaxNode, }; @@ -48,7 +48,7 @@ pub(crate) fn convert_match_to_let_else(acc: &mut Assists, ctx: &AssistContext<' other => format!("{{ {other} }}"), }; let extracting_arm_pat = extracting_arm.pat()?; - let extracted_variable = find_extracted_variable(ctx, &extracting_arm)?; + let extracted_variable_positions = find_extracted_variable(ctx, &extracting_arm)?; acc.add( AssistId("convert_match_to_let_else", AssistKind::RefactorRewrite), @@ -56,7 +56,7 @@ pub(crate) fn convert_match_to_let_else(acc: &mut Assists, ctx: &AssistContext<' let_stmt.syntax().text_range(), |builder| { let extracting_arm_pat = - rename_variable(&extracting_arm_pat, extracted_variable, binding); + rename_variable(&extracting_arm_pat, &extracted_variable_positions, binding); builder.replace( let_stmt.syntax().text_range(), format!("let {extracting_arm_pat} = {initializer_expr} else {diverging_arm_expr};"), @@ -95,14 +95,15 @@ fn find_arms( } // Given an extracting arm, find the extracted variable. -fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Option<ast::Name> { +fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Option<Vec<Name>> { match arm.expr()? { ast::Expr::PathExpr(path) => { 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()?; - Some(source.name()?) + let source = + local.sources(ctx.db()).into_iter().map(|x| x.into_ident_pat()?.name()); + source.collect() } _ => None, } @@ -115,27 +116,34 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti } // Rename `extracted` with `binding` in `pat`. -fn rename_variable(pat: &ast::Pat, extracted: ast::Name, binding: ast::Pat) -> SyntaxNode { +fn rename_variable(pat: &ast::Pat, extracted: &[Name], binding: ast::Pat) -> SyntaxNode { let syntax = pat.syntax().clone_for_update(); - let extracted_syntax = syntax.covering_element(extracted.syntax().text_range()); - - // If `extracted` variable is a record field, we should rename it to `binding`, - // otherwise we just need to replace `extracted` with `binding`. - - if let Some(record_pat_field) = extracted_syntax.ancestors().find_map(ast::RecordPatField::cast) - { - if let Some(name_ref) = record_pat_field.field_name() { - ted::replace( - record_pat_field.syntax(), - ast::make::record_pat_field(ast::make::name_ref(&name_ref.text()), binding) + let extracted = extracted + .iter() + .map(|e| syntax.covering_element(e.syntax().text_range())) + .collect::<Vec<_>>(); + for extracted_syntax in extracted { + // If `extracted` variable is a record field, we should rename it to `binding`, + // otherwise we just need to replace `extracted` with `binding`. + + if let Some(record_pat_field) = + extracted_syntax.ancestors().find_map(ast::RecordPatField::cast) + { + if let Some(name_ref) = record_pat_field.field_name() { + ted::replace( + record_pat_field.syntax(), + ast::make::record_pat_field( + ast::make::name_ref(&name_ref.text()), + binding.clone(), + ) .syntax() .clone_for_update(), - ); + ); + } + } else { + ted::replace(extracted_syntax, binding.clone().syntax().clone_for_update()); } - } else { - ted::replace(extracted_syntax, binding.syntax().clone_for_update()); } - syntax } @@ -163,6 +171,39 @@ fn foo(opt: Option<()>) { } #[test] + fn or_pattern_multiple_binding() { + check_assist( + convert_match_to_let_else, + r#" +//- minicore: option +enum Foo { + A(u32), + B(u32), + C(String), +} + +fn foo(opt: Option<Foo>) -> Result<u32, ()> { + let va$0lue = match opt { + Some(Foo::A(it) | Foo::B(it)) => it, + _ => return Err(()), + }; +} + "#, + r#" +enum Foo { + A(u32), + B(u32), + C(String), +} + +fn foo(opt: Option<Foo>) -> Result<u32, ()> { + let Some(Foo::A(value) | Foo::B(value)) = opt else { return Err(()) }; +} + "#, + ); + } + + #[test] fn should_not_be_applicable_if_extracting_arm_is_not_an_identity_expr() { cov_mark::check_count!(extracting_arm_is_not_an_identity_expr, 2); check_assist_not_applicable( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index e04a1dabb..0b90c9ba3 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs index a738deffb..163561412 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_variable.rs @@ -287,7 +287,7 @@ fn foo() { extract_variable, r" fn foo() { - $0{ let x = 0; x }$0 + $0{ let x = 0; x }$0; something_else(); }", r" diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs index d9e00435e..4c61678ea 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/fix_visibility.rs @@ -192,6 +192,10 @@ fn target_data_for_def( target_name = Some(t.name(db)); offset_target_and_file_id(db, t)? } + hir::ModuleDef::TraitAlias(t) => { + target_name = Some(t.name(db)); + offset_target_and_file_id(db, t)? + } hir::ModuleDef::TypeAlias(t) => { target_name = Some(t.name(db)); offset_target_and_file_id(db, t)? diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs index 45b27a63c..076838928 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_function.rs @@ -5,6 +5,7 @@ use ide_db::{ base_db::FileId, defs::{Definition, NameRefClass}, famous_defs::FamousDefs, + helpers::is_editable_crate, path_transform::PathTransform, FxHashMap, FxHashSet, RootDatabase, SnippetCap, }; @@ -65,6 +66,13 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let fn_name = &*name_ref.text(); let TargetInfo { target_module, adt_name, target, file, insert_offset } = fn_target_info(ctx, path, &call, fn_name)?; + + if let Some(m) = target_module { + if !is_editable_crate(m.krate(), ctx.db()) { + return None; + } + } + let function_builder = FunctionBuilder::from_call(ctx, &call, fn_name, target_module, target)?; let text_range = call.syntax().text_range(); let label = format!("Generate {} function", function_builder.fn_name); @@ -141,12 +149,11 @@ fn gen_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let receiver_ty = ctx.sema.type_of_expr(&call.receiver()?)?.original().strip_references(); let adt = receiver_ty.as_adt()?; - let current_module = ctx.sema.scope(call.syntax())?.module(); let target_module = adt.module(ctx.sema.db); - - if current_module.krate() != target_module.krate() { + if !is_editable_crate(target_module.krate(), ctx.db()) { return None; } + let (impl_, file) = get_adt_source(ctx, &adt, fn_name.text().as_str())?; let (target, insert_offset) = get_method_target(ctx, &impl_, &adt)?; @@ -253,7 +260,7 @@ struct FunctionBuilder { params: ast::ParamList, ret_type: Option<ast::RetType>, should_focus_return_type: bool, - needs_pub: bool, + visibility: Visibility, is_async: bool, } @@ -264,12 +271,14 @@ impl FunctionBuilder { ctx: &AssistContext<'_>, call: &ast::CallExpr, fn_name: &str, - target_module: Option<hir::Module>, + target_module: Option<Module>, target: GeneratedFunctionTarget, ) -> Option<Self> { - let needs_pub = target_module.is_some(); let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).map(|it| it.module()))?; + + let current_module = ctx.sema.scope(call.syntax())?.module(); + let visibility = calculate_necessary_visibility(current_module, target_module, ctx); let fn_name = make::name(fn_name); let mut necessary_generic_params = FxHashSet::default(); let params = fn_args( @@ -300,7 +309,7 @@ impl FunctionBuilder { params, ret_type, should_focus_return_type, - needs_pub, + visibility, is_async, }) } @@ -313,8 +322,9 @@ impl FunctionBuilder { target_module: Module, target: GeneratedFunctionTarget, ) -> Option<Self> { - let needs_pub = - !module_is_descendant(&ctx.sema.scope(call.syntax())?.module(), &target_module, ctx); + let current_module = ctx.sema.scope(call.syntax())?.module(); + let visibility = calculate_necessary_visibility(current_module, target_module, ctx); + let fn_name = make::name(&name.text()); let mut necessary_generic_params = FxHashSet::default(); necessary_generic_params.extend(receiver_ty.generic_params(ctx.db())); @@ -346,7 +356,7 @@ impl FunctionBuilder { params, ret_type, should_focus_return_type, - needs_pub, + visibility, is_async, }) } @@ -354,7 +364,11 @@ impl FunctionBuilder { fn render(self, is_method: bool) -> FunctionTemplate { let placeholder_expr = make::ext::expr_todo(); let fn_body = make::block_expr(vec![], Some(placeholder_expr)); - let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; + let visibility = match self.visibility { + Visibility::None => None, + Visibility::Crate => Some(make::visibility_pub_crate()), + Visibility::Pub => Some(make::visibility_pub()), + }; let mut fn_def = make::fn_( visibility, self.fn_name, @@ -527,7 +541,7 @@ impl GeneratedFunctionTarget { /// Computes parameter list for the generated function. fn fn_args( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, call: ast::CallableExpr, necessary_generic_params: &mut FxHashSet<hir::GenericParam>, ) -> Option<ast::ParamList> { @@ -957,13 +971,13 @@ fn fn_arg_name(sema: &Semantics<'_, RootDatabase>, arg_expr: &ast::Expr) -> Stri fn fn_arg_type( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, fn_arg: &ast::Expr, generic_params: &mut FxHashSet<hir::GenericParam>, ) -> String { fn maybe_displayed_type( ctx: &AssistContext<'_>, - target_module: hir::Module, + target_module: Module, fn_arg: &ast::Expr, generic_params: &mut FxHashSet<hir::GenericParam>, ) -> Option<String> { @@ -1013,7 +1027,7 @@ fn next_space_for_fn_after_call_site(expr: ast::CallableExpr) -> Option<Generate } fn next_space_for_fn_in_module( - db: &dyn hir::db::AstDatabase, + db: &dyn hir::db::ExpandDatabase, module_source: &hir::InFile<hir::ModuleSource>, ) -> Option<(FileId, GeneratedFunctionTarget)> { let file = module_source.file_id.original_file(db); @@ -1048,16 +1062,29 @@ fn next_space_for_fn_in_impl(impl_: &ast::Impl) -> Option<GeneratedFunctionTarge } } -fn module_is_descendant(module: &hir::Module, ans: &hir::Module, ctx: &AssistContext<'_>) -> bool { - if module == ans { - return true; - } - for c in ans.children(ctx.sema.db) { - if module_is_descendant(module, &c, ctx) { - return true; - } +#[derive(Clone, Copy)] +enum Visibility { + None, + Crate, + Pub, +} + +fn calculate_necessary_visibility( + current_module: Module, + target_module: Module, + ctx: &AssistContext<'_>, +) -> Visibility { + let db = ctx.db(); + let current_module = current_module.nearest_non_block_module(db); + let target_module = target_module.nearest_non_block_module(db); + + if target_module.krate() != current_module.krate() { + Visibility::Pub + } else if current_module.path_to_root(db).contains(&target_module) { + Visibility::None + } else { + Visibility::Crate } - false } // This is never intended to be used as a generic graph strucuture. If there's ever another need of @@ -2656,4 +2683,79 @@ fn main() { ", ) } + + #[test] + fn applicable_in_different_local_crate() { + check_assist( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:local +fn dummy() {} +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::foo$0(); +} +", + r" +fn dummy() {} + +pub fn foo() ${0:-> _} { + todo!() +} +", + ); + } + + #[test] + fn applicable_in_different_local_crate_method() { + check_assist( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:local +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::S.foo$0(); +} +", + r" +pub struct S; +impl S { + pub fn foo(&self) ${0:-> _} { + todo!() + } +} +", + ); + } + + #[test] + fn not_applicable_in_different_library_crate() { + check_assist_not_applicable( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:library +fn dummy() {} +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::foo$0(); +} +", + ); + } + + #[test] + fn not_applicable_in_different_library_crate_method() { + check_assist_not_applicable( + generate_function, + r" +//- /lib.rs crate:lib new_source_root:library +pub struct S; +//- /main.rs crate:main deps:lib new_source_root:local +fn main() { + lib::S.foo$0(); +} +", + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs index 9ce525ca3..442918619 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_is_empty_from_len.rs @@ -95,14 +95,7 @@ fn get_impl_method( let scope = ctx.sema.scope(impl_.syntax())?; let ty = impl_def.self_ty(db); - ty.iterate_method_candidates( - db, - &scope, - &scope.visible_traits().0, - None, - Some(fn_name), - |func| Some(func), - ) + ty.iterate_method_candidates(db, &scope, None, Some(fn_name), |func| Some(func)) } #[cfg(test)] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs index 8d311262a..e30a3e942 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_new.rs @@ -1,5 +1,5 @@ use ide_db::{ - imports::import_assets::item_for_path_search, use_trivial_contructor::use_trivial_constructor, + imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, }; use itertools::Itertools; use stdx::format_to; diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs index 5ac18727c..28d815e81 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_call.rs @@ -363,10 +363,10 @@ fn inline( .collect(); if function.self_param(sema.db).is_some() { - let this = || make::name_ref("this").syntax().clone_for_update(); + let this = || make::name_ref("this").syntax().clone_for_update().first_token().unwrap(); if let Some(self_local) = params[0].2.as_local(sema.db) { usages_for_locals(self_local) - .flat_map(|FileReference { name, range, .. }| match name { + .filter_map(|FileReference { name, range, .. }| match name { ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)), _ => None, }) @@ -691,6 +691,42 @@ fn main() { } #[test] + fn generic_method_by_ref() { + check_assist( + inline_call, + r#" +struct Foo(u32); + +impl Foo { + fn add<T>(&self, a: u32) -> Self { + Foo(self.0 + a) + } +} + +fn main() { + let x = Foo(3).add$0::<usize>(2); +} +"#, + r#" +struct Foo(u32); + +impl Foo { + fn add<T>(&self, a: u32) -> Self { + Foo(self.0 + a) + } +} + +fn main() { + let x = { + let ref this = Foo(3); + Foo(this.0 + 2) + }; +} +"#, + ); + } + + #[test] fn method_by_ref_mut() { check_assist( inline_call, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs index ce44100e3..e69d1a296 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/inline_local_variable.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs index 52dd670ec..a403d5bc6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_dbg.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use syntax::{ - ast::{self, AstNode, AstToken}, - match_ast, NodeOrToken, SyntaxElement, TextRange, TextSize, T, + ast::{self, make, AstNode, AstToken}, + match_ast, ted, NodeOrToken, SyntaxElement, TextRange, TextSize, T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -12,24 +12,28 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // // ``` // fn main() { -// $0dbg!(92); +// let x = $0dbg!(42 * dbg!(4 + 2));$0 // } // ``` // -> // ``` // fn main() { -// 92; +// let x = 42 * (4 + 2); // } // ``` pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let macro_calls = if ctx.has_empty_selection() { - vec![ctx.find_node_at_offset::<ast::MacroCall>()?] + vec![ctx.find_node_at_offset::<ast::MacroExpr>()?] } else { ctx.covering_element() .as_node()? .descendants() .filter(|node| ctx.selection_trimmed().contains_range(node.text_range())) + // When the selection exactly covers the macro call to be removed, `covering_element()` + // returns `ast::MacroCall` instead of its parent `ast::MacroExpr` that we want. So + // first try finding `ast::MacroCall`s and then retrieve their parent. .filter_map(ast::MacroCall::cast) + .filter_map(|it| it.syntax().parent().and_then(ast::MacroExpr::cast)) .collect() }; @@ -42,16 +46,27 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( acc.add( AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", - ctx.selection_trimmed(), + replacements.iter().map(|&(range, _)| range).reduce(|acc, range| acc.cover(range)).unwrap(), |builder| { - for (range, text) in replacements { - builder.replace(range, text); + for (range, expr) in replacements { + if let Some(expr) = expr { + builder.replace(range, expr.to_string()); + } else { + builder.delete(range); + } } }, ) } -fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, String)> { +/// Returns `None` when either +/// - macro call is not `dbg!()` +/// - any node inside `dbg!()` could not be parsed as an expression +/// - (`macro_expr` has no parent - is that possible?) +/// +/// Returns `Some(_, None)` when the macro call should just be removed. +fn compute_dbg_replacement(macro_expr: ast::MacroExpr) -> Option<(TextRange, Option<ast::Expr>)> { + let macro_call = macro_expr.macro_call()?; let tt = macro_call.token_tree()?; let r_delim = NodeOrToken::Token(tt.right_delimiter_token()?); if macro_call.path()?.segment()?.name_ref()?.text() != "dbg" @@ -68,20 +83,19 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str .map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join(""))) .collect::<Option<Vec<ast::Expr>>>()?; - let macro_expr = ast::MacroExpr::cast(macro_call.syntax().parent()?)?; let parent = macro_expr.syntax().parent()?; Some(match &*input_expressions { // dbg!() [] => { match_ast! { match parent { - ast::StmtList(__) => { + ast::StmtList(_) => { let range = macro_expr.syntax().text_range(); let range = match whitespace_start(macro_expr.syntax().prev_sibling_or_token()) { Some(start) => range.cover_offset(start), None => range, }; - (range, String::new()) + (range, None) }, ast::ExprStmt(it) => { let range = it.syntax().text_range(); @@ -89,19 +103,23 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str Some(start) => range.cover_offset(start), None => range, }; - (range, String::new()) + (range, None) }, - _ => (macro_call.syntax().text_range(), "()".to_owned()) + _ => (macro_call.syntax().text_range(), Some(make::expr_unit())), } } } // dbg!(expr0) [expr] => { + // dbg!(expr, &parent); let wrap = match ast::Expr::cast(parent) { Some(parent) => match (expr, parent) { (ast::Expr::CastExpr(_), ast::Expr::CastExpr(_)) => false, ( - ast::Expr::BoxExpr(_) | ast::Expr::PrefixExpr(_) | ast::Expr::RefExpr(_), + ast::Expr::BoxExpr(_) + | ast::Expr::PrefixExpr(_) + | ast::Expr::RefExpr(_) + | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::CastExpr(_) @@ -112,7 +130,10 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str | ast::Expr::TryExpr(_), ) => true, ( - ast::Expr::BinExpr(_) | ast::Expr::CastExpr(_) | ast::Expr::RangeExpr(_), + ast::Expr::BinExpr(_) + | ast::Expr::CastExpr(_) + | ast::Expr::RangeExpr(_) + | ast::Expr::MacroExpr(_), ast::Expr::AwaitExpr(_) | ast::Expr::BinExpr(_) | ast::Expr::CallExpr(_) @@ -129,16 +150,61 @@ fn compute_dbg_replacement(macro_call: ast::MacroCall) -> Option<(TextRange, Str }, None => false, }; - ( - macro_call.syntax().text_range(), - if wrap { format!("({expr})") } else { expr.to_string() }, - ) + let expr = replace_nested_dbgs(expr.clone()); + let expr = if wrap { make::expr_paren(expr) } else { expr.clone_subtree() }; + (macro_call.syntax().text_range(), Some(expr)) } // dbg!(expr0, expr1, ...) - exprs => (macro_call.syntax().text_range(), format!("({})", exprs.iter().format(", "))), + exprs => { + let exprs = exprs.iter().cloned().map(replace_nested_dbgs); + let expr = make::expr_tuple(exprs); + (macro_call.syntax().text_range(), Some(expr)) + } }) } +fn replace_nested_dbgs(expanded: ast::Expr) -> ast::Expr { + if let ast::Expr::MacroExpr(mac) = &expanded { + // Special-case when `expanded` itself is `dbg!()` since we cannot replace the whole tree + // with `ted`. It should be fairly rare as it means the user wrote `dbg!(dbg!(..))` but you + // never know how code ends up being! + let replaced = if let Some((_, expr_opt)) = compute_dbg_replacement(mac.clone()) { + match expr_opt { + Some(expr) => expr, + None => { + stdx::never!("dbg! inside dbg! should not be just removed"); + expanded + } + } + } else { + expanded + }; + + return replaced; + } + + let expanded = expanded.clone_for_update(); + + // We need to collect to avoid mutation during traversal. + let macro_exprs: Vec<_> = + expanded.syntax().descendants().filter_map(ast::MacroExpr::cast).collect(); + + for mac in macro_exprs { + let expr_opt = match compute_dbg_replacement(mac.clone()) { + Some((_, expr)) => expr, + None => continue, + }; + + if let Some(expr) = expr_opt { + ted::replace(mac.syntax(), expr.syntax().clone_for_update()); + } else { + ted::remove(mac.syntax()); + } + } + + expanded +} + fn whitespace_start(it: Option<SyntaxElement>) -> Option<TextSize> { Some(it?.into_token().and_then(ast::Whitespace::cast)?.syntax().text_range().start()) } @@ -287,4 +353,32 @@ fn f() { check_assist_not_applicable(remove_dbg, r#"$0dbg$0!(0)"#); check_assist_not_applicable(remove_dbg, r#"$0dbg!(0$0)"#); } + + #[test] + fn test_nested_dbg() { + check( + r#"$0let x = dbg!(dbg!(dbg!(dbg!(0 + 1)) * 2) + dbg!(3));$0"#, + r#"let x = ((0 + 1) * 2) + 3;"#, + ); + check(r#"$0dbg!(10, dbg!(), dbg!(20, 30))$0"#, r#"(10, (), (20, 30))"#); + } + + #[test] + fn test_multiple_nested_dbg() { + check( + r#" +fn f() { + $0dbg!(); + let x = dbg!(dbg!(dbg!(0 + 1)) + 2) + dbg!(3); + dbg!(10, dbg!(), dbg!(20, 30));$0 +} +"#, + r#" +fn f() { + let x = ((0 + 1) + 2) + 3; + (10, (), (20, 30)); +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs index 457559656..5e31d38fb 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_if_let_with_match.rs @@ -102,9 +102,11 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext<' return None; } + let let_ = if pat_seen { " let" } else { "" }; + acc.add( AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite), - "Replace if let with match", + format!("Replace if{let_} with match"), available_range, move |edit| { let match_expr = { @@ -210,8 +212,17 @@ fn make_else_arm( // ``` pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?; + let match_arm_list = match_expr.match_arm_list()?; + let available_range = TextRange::new( + match_expr.syntax().text_range().start(), + match_arm_list.syntax().text_range().start(), + ); + let cursor_in_range = available_range.contains_range(ctx.selection_trimmed()); + if !cursor_in_range { + return None; + } - let mut arms = match_expr.match_arm_list()?.arms(); + let mut arms = match_arm_list.arms(); let (first_arm, second_arm) = (arms.next()?, arms.next()?); if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() { return None; @@ -226,10 +237,20 @@ pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext<' )?; let scrutinee = match_expr.expr()?; + let let_ = match &if_let_pat { + ast::Pat::LiteralPat(p) + if p.literal() + .map(|it| it.token().kind()) + .map_or(false, |it| it == T![true] || it == T![false]) => + { + "" + } + _ => " let", + }; let target = match_expr.syntax().text_range(); acc.add( AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite), - "Replace match with if let", + format!("Replace match with if{let_}"), target, move |edit| { fn make_block_expr(expr: ast::Expr) -> ast::BlockExpr { diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs new file mode 100644 index 000000000..a7e3ed793 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_method_eager_lazy.rs @@ -0,0 +1,310 @@ +use ide_db::assists::{AssistId, AssistKind}; +use syntax::{ + ast::{self, make, Expr, HasArgList}, + AstNode, +}; + +use crate::{AssistContext, Assists}; + +// Assist: replace_with_lazy_method +// +// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`. +// +// ``` +// # //- minicore:option, fn +// fn foo() { +// let a = Some(1); +// a.unwra$0p_or(2); +// } +// ``` +// -> +// ``` +// fn foo() { +// let a = Some(1); +// a.unwrap_or_else(|| 2); +// } +// ``` +pub(crate) fn replace_with_lazy_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + let scope = ctx.sema.scope(call.syntax())?; + + let last_arg = call.arg_list()?.args().next()?; + let method_name = call.name_ref()?; + + let callable = ctx.sema.resolve_method_call_as_callable(&call)?; + let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?; + let n_params = callable.n_params() + 1; + + let method_name_lazy = format!( + "{method_name}{}", + if method_name.text().ends_with("or") { "_else" } else { "_with" } + ); + + receiver_ty.iterate_method_candidates_with_traits( + ctx.sema.db, + &scope, + &scope.visible_traits().0, + None, + None, + |func| { + let valid = func.name(ctx.sema.db).as_str() == Some(&*method_name_lazy) + && func.num_params(ctx.sema.db) == n_params + && { + let params = func.params_without_self(ctx.sema.db); + let last_p = params.first()?; + // FIXME: Check that this has the form of `() -> T` where T is the current type of the argument + last_p.ty().impls_fnonce(ctx.sema.db) + }; + valid.then_some(func) + }, + )?; + + acc.add( + AssistId("replace_with_lazy_method", AssistKind::RefactorRewrite), + format!("Replace {method_name} with {method_name_lazy}"), + call.syntax().text_range(), + |builder| { + builder.replace(method_name.syntax().text_range(), method_name_lazy); + let closured = into_closure(&last_arg); + builder.replace_ast(last_arg, closured); + }, + ) +} + +fn into_closure(param: &Expr) -> Expr { + (|| { + if let ast::Expr::CallExpr(call) = param { + if call.arg_list()?.args().count() == 0 { + Some(call.expr()?) + } else { + None + } + } else { + None + } + })() + .unwrap_or_else(|| make::expr_closure(None, param.clone())) +} + +// Assist: replace_with_eager_method +// +// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`. +// +// ``` +// # //- minicore:option, fn +// fn foo() { +// let a = Some(1); +// a.unwra$0p_or_else(|| 2); +// } +// ``` +// -> +// ``` +// fn foo() { +// let a = Some(1); +// a.unwrap_or(2); +// } +// ``` +pub(crate) fn replace_with_eager_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; + let scope = ctx.sema.scope(call.syntax())?; + + let last_arg = call.arg_list()?.args().next()?; + let method_name = call.name_ref()?; + + let callable = ctx.sema.resolve_method_call_as_callable(&call)?; + let (_, receiver_ty) = callable.receiver_param(ctx.sema.db)?; + let n_params = callable.n_params() + 1; + let params = callable.params(ctx.sema.db); + + // FIXME: Check that the arg is of the form `() -> T` + if !params.first()?.1.impls_fnonce(ctx.sema.db) { + return None; + } + + let method_name_text = method_name.text(); + let method_name_eager = method_name_text + .strip_suffix("_else") + .or_else(|| method_name_text.strip_suffix("_with"))?; + + receiver_ty.iterate_method_candidates_with_traits( + ctx.sema.db, + &scope, + &scope.visible_traits().0, + None, + None, + |func| { + let valid = func.name(ctx.sema.db).as_str() == Some(&*method_name_eager) + && func.num_params(ctx.sema.db) == n_params; + valid.then_some(func) + }, + )?; + + acc.add( + AssistId("replace_with_eager_method", AssistKind::RefactorRewrite), + format!("Replace {method_name} with {method_name_eager}"), + call.syntax().text_range(), + |builder| { + builder.replace(method_name.syntax().text_range(), method_name_eager); + let called = into_call(&last_arg); + builder.replace_ast(last_arg, called); + }, + ) +} + +fn into_call(param: &Expr) -> Expr { + (|| { + if let ast::Expr::ClosureExpr(closure) = param { + if closure.param_list()?.params().count() == 0 { + Some(closure.body()?) + } else { + None + } + } else { + None + } + })() + .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new()))) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn replace_or_with_or_else_simple() { + check_assist( + replace_with_lazy_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or(2); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(|| 2); +} +"#, + ) + } + + #[test] + fn replace_or_with_or_else_call() { + check_assist( + replace_with_lazy_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or(x()); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(x); +} +"#, + ) + } + + #[test] + fn replace_or_with_or_else_block() { + check_assist( + replace_with_lazy_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or({ + let mut x = bar(); + for i in 0..10 { + x += i; + } + x + }); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or_else(|| { + let mut x = bar(); + for i in 0..10 { + x += i; + } + x + }); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_simple() { + check_assist( + replace_with_eager_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or_else(|| 2); +} +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or(2); +} +"#, + ) + } + + #[test] + fn replace_or_else_with_or_call() { + check_assist( + replace_with_eager_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some(1); + return foo.unwrap_$0or_else(x); +} + +fn x() -> i32 { 0 } +"#, + r#" +fn foo() { + let foo = Some(1); + return foo.unwrap_or(x()); +} + +fn x() -> i32 { 0 } +"#, + ) + } + + #[test] + fn replace_or_else_with_or_map() { + check_assist( + replace_with_eager_method, + r#" +//- minicore: option, fn +fn foo() { + let foo = Some("foo"); + return foo.map$0_or_else(|| 42, |v| v.len()); +} +"#, + r#" +fn foo() { + let foo = Some("foo"); + return foo.map_or(42, |v| v.len()); +} +"#, + ) + } +} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs deleted file mode 100644 index f0ed3c4fe..000000000 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/replace_or_with_or_else.rs +++ /dev/null @@ -1,364 +0,0 @@ -use ide_db::{ - assists::{AssistId, AssistKind}, - famous_defs::FamousDefs, -}; -use syntax::{ - ast::{self, make, Expr, HasArgList}, - AstNode, -}; - -use crate::{AssistContext, Assists}; - -// Assist: replace_or_with_or_else -// -// Replace `unwrap_or` with `unwrap_or_else` and `ok_or` with `ok_or_else`. -// -// ``` -// # //- minicore:option -// fn foo() { -// let a = Some(1); -// a.unwra$0p_or(2); -// } -// ``` -// -> -// ``` -// fn foo() { -// let a = Some(1); -// a.unwrap_or_else(|| 2); -// } -// ``` -pub(crate) fn replace_or_with_or_else(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; - - let kind = is_option_or_result(call.receiver()?, ctx)?; - - let (name, arg_list) = (call.name_ref()?, call.arg_list()?); - - let mut map_or = false; - - let replace = match &*name.text() { - "unwrap_or" => "unwrap_or_else".to_string(), - "or" => "or_else".to_string(), - "ok_or" if kind == Kind::Option => "ok_or_else".to_string(), - "map_or" => { - map_or = true; - "map_or_else".to_string() - } - _ => return None, - }; - - let arg = match arg_list.args().collect::<Vec<_>>().as_slice() { - [] => make::arg_list(Vec::new()), - [first] => { - let param = into_closure(first); - make::arg_list(vec![param]) - } - [first, second] if map_or => { - let param = into_closure(first); - make::arg_list(vec![param, second.clone()]) - } - _ => return None, - }; - - acc.add( - AssistId("replace_or_with_or_else", AssistKind::RefactorRewrite), - format!("Replace {name} with {replace}"), - call.syntax().text_range(), - |builder| { - builder.replace(name.syntax().text_range(), replace); - builder.replace_ast(arg_list, arg) - }, - ) -} - -fn into_closure(param: &Expr) -> Expr { - (|| { - if let ast::Expr::CallExpr(call) = param { - if call.arg_list()?.args().count() == 0 { - Some(call.expr()?) - } else { - None - } - } else { - None - } - })() - .unwrap_or_else(|| make::expr_closure(None, param.clone())) -} - -// Assist: replace_or_else_with_or -// -// Replace `unwrap_or_else` with `unwrap_or` and `ok_or_else` with `ok_or`. -// -// ``` -// # //- minicore:option -// fn foo() { -// let a = Some(1); -// a.unwra$0p_or_else(|| 2); -// } -// ``` -// -> -// ``` -// fn foo() { -// let a = Some(1); -// a.unwrap_or(2); -// } -// ``` -pub(crate) fn replace_or_else_with_or(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let call: ast::MethodCallExpr = ctx.find_node_at_offset()?; - - let kind = is_option_or_result(call.receiver()?, ctx)?; - - let (name, arg_list) = (call.name_ref()?, call.arg_list()?); - - let mut map_or = false; - let replace = match &*name.text() { - "unwrap_or_else" => "unwrap_or".to_string(), - "or_else" => "or".to_string(), - "ok_or_else" if kind == Kind::Option => "ok_or".to_string(), - "map_or_else" => { - map_or = true; - "map_or".to_string() - } - _ => return None, - }; - - let arg = match arg_list.args().collect::<Vec<_>>().as_slice() { - [] => make::arg_list(Vec::new()), - [first] => { - let param = into_call(first); - make::arg_list(vec![param]) - } - [first, second] if map_or => { - let param = into_call(first); - make::arg_list(vec![param, second.clone()]) - } - _ => return None, - }; - - acc.add( - AssistId("replace_or_else_with_or", AssistKind::RefactorRewrite), - format!("Replace {name} with {replace}"), - call.syntax().text_range(), - |builder| { - builder.replace(name.syntax().text_range(), replace); - builder.replace_ast(arg_list, arg) - }, - ) -} - -fn into_call(param: &Expr) -> Expr { - (|| { - if let ast::Expr::ClosureExpr(closure) = param { - if closure.param_list()?.params().count() == 0 { - Some(closure.body()?) - } else { - None - } - } else { - None - } - })() - .unwrap_or_else(|| make::expr_call(param.clone(), make::arg_list(Vec::new()))) -} - -#[derive(PartialEq, Eq)] -enum Kind { - Option, - Result, -} - -fn is_option_or_result(receiver: Expr, ctx: &AssistContext<'_>) -> Option<Kind> { - let ty = ctx.sema.type_of_expr(&receiver)?.adjusted().as_adt()?.as_enum()?; - let option_enum = - FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_option_Option(); - - if let Some(option_enum) = option_enum { - if ty == option_enum { - return Some(Kind::Option); - } - } - - let result_enum = - FamousDefs(&ctx.sema, ctx.sema.scope(receiver.syntax())?.krate()).core_result_Result(); - - if let Some(result_enum) = result_enum { - if ty == result_enum { - return Some(Kind::Result); - } - } - - None -} - -#[cfg(test)] -mod tests { - use crate::tests::{check_assist, check_assist_not_applicable}; - - use super::*; - - #[test] - fn replace_or_with_or_else_simple() { - check_assist( - replace_or_with_or_else, - r#" -//- minicore: option -fn foo() { - let foo = Some(1); - return foo.unwrap_$0or(2); -} -"#, - r#" -fn foo() { - let foo = Some(1); - return foo.unwrap_or_else(|| 2); -} -"#, - ) - } - - #[test] - fn replace_or_with_or_else_call() { - check_assist( - replace_or_with_or_else, - r#" -//- minicore: option -fn foo() { - let foo = Some(1); - return foo.unwrap_$0or(x()); -} -"#, - r#" -fn foo() { - let foo = Some(1); - return foo.unwrap_or_else(x); -} -"#, - ) - } - - #[test] - fn replace_or_with_or_else_block() { - check_assist( - replace_or_with_or_else, - r#" -//- minicore: option -fn foo() { - let foo = Some(1); - return foo.unwrap_$0or({ - let mut x = bar(); - for i in 0..10 { - x += i; - } - x - }); -} -"#, - r#" -fn foo() { - let foo = Some(1); - return foo.unwrap_or_else(|| { - let mut x = bar(); - for i in 0..10 { - x += i; - } - x - }); -} -"#, - ) - } - - #[test] - fn replace_or_else_with_or_simple() { - check_assist( - replace_or_else_with_or, - r#" -//- minicore: option -fn foo() { - let foo = Some(1); - return foo.unwrap_$0or_else(|| 2); -} -"#, - r#" -fn foo() { - let foo = Some(1); - return foo.unwrap_or(2); -} -"#, - ) - } - - #[test] - fn replace_or_else_with_or_call() { - check_assist( - replace_or_else_with_or, - r#" -//- minicore: option -fn foo() { - let foo = Some(1); - return foo.unwrap_$0or_else(x); -} -"#, - r#" -fn foo() { - let foo = Some(1); - return foo.unwrap_or(x()); -} -"#, - ) - } - - #[test] - fn replace_or_else_with_or_result() { - check_assist( - replace_or_else_with_or, - r#" -//- minicore: result -fn foo() { - let foo = Ok(1); - return foo.unwrap_$0or_else(x); -} -"#, - r#" -fn foo() { - let foo = Ok(1); - return foo.unwrap_or(x()); -} -"#, - ) - } - - #[test] - fn replace_or_else_with_or_map() { - check_assist( - replace_or_else_with_or, - r#" -//- minicore: result -fn foo() { - let foo = Ok("foo"); - return foo.map$0_or_else(|| 42, |v| v.len()); -} -"#, - r#" -fn foo() { - let foo = Ok("foo"); - return foo.map_or(42, |v| v.len()); -} -"#, - ) - } - - #[test] - fn replace_or_else_with_or_not_applicable() { - check_assist_not_applicable( - replace_or_else_with_or, - r#" -fn foo() { - let foo = Ok(1); - return foo.unwrap_$0or_else(x); -} -"#, - ) - } -} diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 276cf5f5d..8b07e29a5 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -188,7 +188,7 @@ mod handlers { mod replace_try_expr_with_match; mod replace_derive_with_manual_impl; mod replace_if_let_with_match; - mod replace_or_with_or_else; + mod replace_method_eager_lazy; mod replace_arith_op; mod introduce_named_generic; mod replace_let_with_if_let; @@ -265,7 +265,6 @@ mod handlers { inline_local_variable::inline_local_variable, inline_type_alias::inline_type_alias, inline_type_alias::inline_type_alias_uses, - inline_macro::inline_macro, introduce_named_generic::introduce_named_generic, introduce_named_lifetime::introduce_named_lifetime, invert_if::invert_if, @@ -286,7 +285,6 @@ mod handlers { raw_string::add_hash, raw_string::make_usual_string, raw_string::remove_hash, - remove_dbg::remove_dbg, remove_mut::remove_mut, remove_unused_param::remove_unused_param, remove_parentheses::remove_parentheses, @@ -297,8 +295,8 @@ mod handlers { replace_if_let_with_match::replace_if_let_with_match, replace_if_let_with_match::replace_match_with_if_let, replace_let_with_if_let::replace_let_with_if_let, - replace_or_with_or_else::replace_or_else_with_or, - replace_or_with_or_else::replace_or_with_or_else, + replace_method_eager_lazy::replace_with_eager_method, + replace_method_eager_lazy::replace_with_lazy_method, replace_turbofish_with_explicit_type::replace_turbofish_with_explicit_type, replace_qualified_name_with_use::replace_qualified_name_with_use, replace_arith_op::replace_arith_with_wrapping, @@ -335,6 +333,9 @@ mod handlers { generate_setter::generate_setter, generate_delegate_methods::generate_delegate_methods, generate_deref::generate_deref, + // + remove_dbg::remove_dbg, + inline_macro::inline_macro, // Are you sure you want to add new assist here, and not to the // sorted list above? ] diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index 8a25e1f64..e5a8d675a 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -2006,12 +2006,12 @@ fn doctest_remove_dbg() { "remove_dbg", r#####" fn main() { - $0dbg!(92); + let x = $0dbg!(42 * dbg!(4 + 2));$0 } "#####, r#####" fn main() { - 92; + let x = 42 * (4 + 2); } "#####, ) @@ -2314,46 +2314,6 @@ fn handle(action: Action) { } #[test] -fn doctest_replace_or_else_with_or() { - check_doc_test( - "replace_or_else_with_or", - r#####" -//- minicore:option -fn foo() { - let a = Some(1); - a.unwra$0p_or_else(|| 2); -} -"#####, - r#####" -fn foo() { - let a = Some(1); - a.unwrap_or(2); -} -"#####, - ) -} - -#[test] -fn doctest_replace_or_with_or_else() { - check_doc_test( - "replace_or_with_or_else", - r#####" -//- minicore:option -fn foo() { - let a = Some(1); - a.unwra$0p_or(2); -} -"#####, - r#####" -fn foo() { - let a = Some(1); - a.unwrap_or_else(|| 2); -} -"#####, - ) -} - -#[test] fn doctest_replace_qualified_name_with_use() { check_doc_test( "replace_qualified_name_with_use", @@ -2428,6 +2388,46 @@ fn main() { } #[test] +fn doctest_replace_with_eager_method() { + check_doc_test( + "replace_with_eager_method", + r#####" +//- minicore:option, fn +fn foo() { + let a = Some(1); + a.unwra$0p_or_else(|| 2); +} +"#####, + r#####" +fn foo() { + let a = Some(1); + a.unwrap_or(2); +} +"#####, + ) +} + +#[test] +fn doctest_replace_with_lazy_method() { + check_doc_test( + "replace_with_lazy_method", + r#####" +//- minicore:option, fn +fn foo() { + let a = Some(1); + a.unwra$0p_or(2); +} +"#####, + r#####" +fn foo() { + let a = Some(1); + a.unwrap_or_else(|| 2); +} +"#####, + ) +} + +#[test] fn doctest_sort_items() { check_doc_test( "sort_items", diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs index eb87d6c58..c3136f6df 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions.rs @@ -23,7 +23,7 @@ pub(crate) mod env_vars; use std::iter; -use hir::{known, ScopeDef}; +use hir::{known, ScopeDef, Variant}; use ide_db::{imports::import_assets::LocatedImport, SymbolKind}; use syntax::ast; @@ -537,17 +537,20 @@ fn enum_variants_with_paths( impl_: &Option<ast::Impl>, cb: impl Fn(&mut Completions, &CompletionContext<'_>, hir::Variant, hir::ModPath), ) { + let mut process_variant = |variant: Variant| { + let self_path = hir::ModPath::from_segments( + hir::PathKind::Plain, + iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))), + ); + + cb(acc, ctx, variant, self_path); + }; + let variants = enum_.variants(ctx.db); if let Some(impl_) = impl_.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) { if impl_.self_ty(ctx.db).as_adt() == Some(hir::Adt::Enum(enum_)) { - for &variant in &variants { - let self_path = hir::ModPath::from_segments( - hir::PathKind::Plain, - iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))), - ); - cb(acc, ctx, variant, self_path); - } + variants.iter().for_each(|variant| process_variant(*variant)); } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs index 7c6e5e100..77246379e 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs @@ -122,7 +122,7 @@ fn complete_methods( mut f: impl FnMut(hir::Function), ) { let mut seen_methods = FxHashSet::default(); - receiver.iterate_method_candidates( + receiver.iterate_method_candidates_with_traits( ctx.db, &ctx.scope, &ctx.traits_in_scope(), @@ -415,7 +415,6 @@ fn foo(a: lib::A) { a.$0 } fn test_local_impls() { check( r#" -//- /lib.rs crate:lib pub struct A {} mod m { impl super::A { @@ -427,9 +426,8 @@ mod m { } } } -//- /main.rs crate:main deps:lib -fn foo(a: lib::A) { - impl lib::A { +fn foo(a: A) { + impl A { fn local_method(&self) {} } a.$0 diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs index 364969af9..0979f6a6d 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/flyimport.rs @@ -5,10 +5,7 @@ use ide_db::imports::{ insert_use::ImportScope, }; use itertools::Itertools; -use syntax::{ - ast::{self}, - AstNode, SyntaxNode, T, -}; +use syntax::{ast, AstNode, SyntaxNode, T}; use crate::{ context::{ diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs index 37849c251..69c05a76d 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/type.rs @@ -33,7 +33,9 @@ pub(crate) fn complete_type_path( // Don't suggest attribute macros and derives. ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db), // Type things are fine - ScopeDef::ModuleDef(BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TypeAlias(_)) + ScopeDef::ModuleDef( + BuiltinType(_) | Adt(_) | Module(_) | Trait(_) | TraitAlias(_) | TypeAlias(_), + ) | ScopeDef::AdtSelfType(_) | ScopeDef::Unknown | ScopeDef::GenericParam(TypeParam(_)) => true, diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index ea54068b0..8cbf89e9c 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -6,13 +6,13 @@ mod tests; use std::iter; -use base_db::SourceDatabaseExt; use hir::{ HasAttrs, Local, Name, PathResolution, ScopeDef, Semantics, SemanticsScope, Type, TypeInfo, }; use ide_db::{ base_db::{FilePosition, SourceDatabase}, famous_defs::FamousDefs, + helpers::is_editable_crate, FxHashMap, FxHashSet, RootDatabase, }; use syntax::{ @@ -220,6 +220,8 @@ pub(super) struct PatternContext { /// The record pattern this name or ref is a field of pub(super) record_pat: Option<ast::RecordPat>, pub(super) impl_: Option<ast::Impl>, + /// List of missing variants in a match expr + pub(super) missing_variants: Vec<hir::Variant>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -387,8 +389,7 @@ pub(crate) struct CompletionContext<'a> { impl<'a> CompletionContext<'a> { /// The range of the identifier that is being completed. pub(crate) fn source_range(&self) -> TextRange { - // check kind of macro-expanded token, but use range of original token - let kind = self.token.kind(); + let kind = self.original_token.kind(); match kind { CHAR => { // assume we are completing a lifetime but the user has only typed the ' @@ -416,6 +417,7 @@ impl<'a> CompletionContext<'a> { hir::ModuleDef::Const(it) => self.is_visible(it), hir::ModuleDef::Static(it) => self.is_visible(it), hir::ModuleDef::Trait(it) => self.is_visible(it), + hir::ModuleDef::TraitAlias(it) => self.is_visible(it), hir::ModuleDef::TypeAlias(it) => self.is_visible(it), hir::ModuleDef::Macro(it) => self.is_visible(it), hir::ModuleDef::BuiltinType(_) => Visible::Yes, @@ -525,10 +527,11 @@ impl<'a> CompletionContext<'a> { return Visible::No; } // If the definition location is editable, also show private items - let root_file = defining_crate.root_file(self.db); - let source_root_id = self.db.file_source_root(root_file); - let is_editable = !self.db.source_root(source_root_id).is_library; - return if is_editable { Visible::Editable } else { Visible::No }; + return if is_editable_crate(defining_crate, self.db) { + Visible::Editable + } else { + Visible::No + }; } if self.is_doc_hidden(attrs, defining_crate) { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs index db0045aef..a94c40458 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs @@ -1,7 +1,7 @@ //! Module responsible for analyzing the code surrounding the cursor for completion. use std::iter; -use hir::{Semantics, Type, TypeInfo}; +use hir::{Semantics, Type, TypeInfo, Variant}; use ide_db::{active_parameter::ActiveParameter, RootDatabase}; use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, @@ -353,7 +353,7 @@ fn expected_type_and_name( _ => ty, }; - loop { + let (ty, name) = loop { break match_ast! { match node { ast::LetStmt(it) => { @@ -385,9 +385,7 @@ fn expected_type_and_name( token.clone(), ).map(|ap| { let name = ap.ident().map(NameOrNameRef::Name); - - let ty = strip_refs(ap.ty); - (Some(ty), name) + (Some(ap.ty), name) }) .unwrap_or((None, None)) }, @@ -489,7 +487,8 @@ fn expected_type_and_name( }, } }; - } + }; + (ty.map(strip_refs), name) } fn classify_lifetime( @@ -1133,6 +1132,9 @@ fn pattern_context_for( pat: ast::Pat, ) -> PatternContext { let mut param_ctx = None; + + let mut missing_variants = vec![]; + let (refutability, has_type_ascription) = pat .syntax() @@ -1162,7 +1164,52 @@ fn pattern_context_for( })(); return (PatternRefutability::Irrefutable, has_type_ascription) }, - ast::MatchArm(_) => PatternRefutability::Refutable, + ast::MatchArm(match_arm) => { + let missing_variants_opt = match_arm + .syntax() + .parent() + .and_then(ast::MatchArmList::cast) + .and_then(|match_arm_list| { + match_arm_list + .syntax() + .parent() + .and_then(ast::MatchExpr::cast) + .and_then(|match_expr| { + let expr_opt = find_opt_node_in_file(&original_file, match_expr.expr()); + + expr_opt.and_then(|expr| { + sema.type_of_expr(&expr)? + .adjusted() + .autoderef(sema.db) + .find_map(|ty| match ty.as_adt() { + Some(hir::Adt::Enum(e)) => Some(e), + _ => None, + }).and_then(|enum_| { + Some(enum_.variants(sema.db)) + }) + }) + }).and_then(|variants| { + Some(variants.iter().filter_map(|variant| { + let variant_name = variant.name(sema.db).to_string(); + + let variant_already_present = match_arm_list.arms().any(|arm| { + arm.pat().and_then(|pat| { + let pat_already_present = pat.syntax().to_string().contains(&variant_name); + pat_already_present.then(|| pat_already_present) + }).is_some() + }); + + (!variant_already_present).then_some(variant.clone()) + }).collect::<Vec<Variant>>()) + }) + }); + + if let Some(missing_variants_) = missing_variants_opt { + missing_variants = missing_variants_; + }; + + PatternRefutability::Refutable + }, ast::LetExpr(_) => PatternRefutability::Refutable, ast::ForExpr(_) => PatternRefutability::Irrefutable, _ => PatternRefutability::Irrefutable, @@ -1184,6 +1231,7 @@ fn pattern_context_for( ref_token, record_pat: None, impl_: fetch_immediate_impl(sema, original_file, pat.syntax()), + missing_variants, } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs index a654a5db5..82a1c10c5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/tests.rs @@ -411,3 +411,15 @@ fn main() { expect!["ty: i32, name: ?"], ); } + +#[test] +fn expected_type_ref_return_pos() { + check_expected_type_and_name( + r#" +fn f(thing: u32) -> &u32 { + &thin$0 +} +"#, + expect!["ty: u32, name: ?"], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs index 2f65491d8..bb9fa7cca 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/item.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/item.rs @@ -288,7 +288,7 @@ impl_from!(SymbolKind for CompletionItemKind); impl CompletionItemKind { #[cfg(test)] - pub(crate) fn tag(&self) -> &'static str { + pub(crate) fn tag(self) -> &'static str { match self { CompletionItemKind::SymbolKind(kind) => match kind { SymbolKind::Attribute => "at", @@ -312,6 +312,7 @@ impl CompletionItemKind { SymbolKind::Struct => "st", SymbolKind::ToolModule => "tm", SymbolKind::Trait => "tt", + SymbolKind::TraitAlias => "tr", SymbolKind::TypeAlias => "ta", SymbolKind::TypeParam => "tp", SymbolKind::Union => "un", diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs index d99ad5f9f..c1f51aabb 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render.rs @@ -367,6 +367,9 @@ fn res_to_kind(resolution: ScopeDef) -> CompletionItemKind { ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::SymbolKind(SymbolKind::Const), ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::SymbolKind(SymbolKind::Static), ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::SymbolKind(SymbolKind::Trait), + ScopeDef::ModuleDef(TraitAlias(..)) => { + CompletionItemKind::SymbolKind(SymbolKind::TraitAlias) + } ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::SymbolKind(SymbolKind::TypeAlias), ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, ScopeDef::GenericParam(param) => CompletionItemKind::SymbolKind(match param { diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs index ffcad1185..44e886076 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/macro_.rs @@ -267,4 +267,63 @@ fn main() { "#, ); } + + #[test] + fn complete_missing_macro_arg() { + // Regression test for https://github.com/rust-lang/rust-analyzer/issues/14246 + check_edit( + "BAR", + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR, $0) +} +"#, + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR, BAR) +} +"#, + ); + check_edit( + "BAR", + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!($0) +} +"#, + r#" +macro_rules! foo { + ($val:ident, $val2: ident) => { + $val $val2 + }; +} + +const BAR: u32 = 9; +fn main() { + foo!(BAR) +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs index 21b4bc217..9225c91be 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/render/pattern.rs @@ -37,7 +37,9 @@ pub(crate) fn render_struct_pat( let lookup = format_literal_lookup(name.as_str(), kind); let pat = render_pat(&ctx, pattern_ctx, &escaped_name, kind, &visible_fields, fields_omitted)?; - Some(build_completion(ctx, label, lookup, pat, strukt)) + let db = ctx.db(); + + Some(build_completion(ctx, label, lookup, pat, strukt, strukt.ty(db), false)) } pub(crate) fn render_variant_pat( @@ -52,6 +54,7 @@ pub(crate) fn render_variant_pat( let fields = variant.fields(ctx.db()); let (visible_fields, fields_omitted) = visible_fields(ctx.completion, &fields, variant)?; + let enum_ty = variant.parent_enum(ctx.db()).ty(ctx.db()); let (name, escaped_name) = match path { Some(path) => (path.unescaped().to_string().into(), path.to_string().into()), @@ -81,7 +84,15 @@ pub(crate) fn render_variant_pat( } }; - Some(build_completion(ctx, label, lookup, pat, variant)) + Some(build_completion( + ctx, + label, + lookup, + pat, + variant, + enum_ty, + pattern_ctx.missing_variants.contains(&variant), + )) } fn build_completion( @@ -90,13 +101,22 @@ fn build_completion( lookup: SmolStr, pat: String, def: impl HasAttrs + Copy, + adt_ty: hir::Type, + // Missing in context of match statement completions + is_variant_missing: bool, ) -> CompletionItem { + let mut relevance = ctx.completion_relevance(); + + if is_variant_missing { + relevance.type_match = super::compute_type_match(ctx.completion, &adt_ty); + } + let mut item = CompletionItem::new(CompletionItemKind::Binding, ctx.source_range(), label); item.set_documentation(ctx.docs(def)) .set_deprecated(ctx.is_deprecated(def)) .detail(&pat) .lookup_by(lookup) - .set_relevance(ctx.completion_relevance()); + .set_relevance(relevance); match ctx.snippet_cap() { Some(snippet_cap) => item.insert_snippet(snippet_cap, pat), None => item.insert_text(pat), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs index 4e60820dd..c97144b61 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs @@ -857,9 +857,9 @@ mod lint { #[test] fn lint_feature() { check_edit( - "box_syntax", + "box_patterns", r#"#[feature(box_$0)] struct Test;"#, - r#"#[feature(box_syntax)] struct Test;"#, + r#"#[feature(box_patterns)] struct Test;"#, ) } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs index ad9254e7f..c0e485c36 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/pattern.rs @@ -614,6 +614,7 @@ fn f(u: U) { check_empty( r#" +#![rustc_coherence_is_core] #[lang = "u32"] impl u32 { pub const MIN: Self = 0; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs index 328faaa06..65cefdb08 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/record.rs @@ -47,6 +47,66 @@ fn foo(s: Struct) { } #[test] +fn record_pattern_field_enum() { + check( + r#" +//- minicore:result +enum Baz { Foo, Bar } + +fn foo(baz: Baz) { + match baz { + Baz::Foo => (), + $0 + } +} +"#, + expect![[r#" + en Baz + en Result + md core + ev Err + ev Ok + bn Baz::Bar Baz::Bar$0 + bn Baz::Foo Baz::Foo$0 + bn Err(…) Err($1)$0 + bn Ok(…) Ok($1)$0 + kw mut + kw ref + "#]], + ); + + check( + r#" +//- minicore:result +enum Baz { Foo, Bar } + +fn foo(baz: Baz) { + use Baz::*; + match baz { + Foo => (), + $0 + } +} + "#, + expect![[r#" + en Baz + en Result + md core + ev Bar + ev Err + ev Foo + ev Ok + bn Bar Bar$0 + bn Err(…) Err($1)$0 + bn Foo Foo$0 + bn Ok(…) Ok($1)$0 + kw mut + kw ref + "#]], + ); +} + +#[test] fn pattern_enum_variant() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs index cb71c7b2b..f8a6f6cd3 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs @@ -608,6 +608,7 @@ fn f() { } //- /core.rs crate:core +#![rustc_coherence_is_core] #[lang = "u8"] impl u8 { pub const MAX: Self = 255; diff --git a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs index 2b6b60547..0da4e729a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/active_parameter.rs @@ -96,6 +96,7 @@ pub fn generic_def_for_node( hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(), + hir::PathResolution::Def(hir::ModuleDef::TraitAlias(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(), hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)) diff --git a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs index b1ee9b58d..ea1d9cc49 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/apply_change.rs @@ -71,70 +71,98 @@ impl RootDatabase { base_db::SourceRootQuery base_db::SourceRootCratesQuery - // AstDatabase + // ExpandDatabase hir::db::AstIdMapQuery + hir::db::ParseMacroExpansionQuery + hir::db::InternMacroCallQuery hir::db::MacroArgTextQuery hir::db::MacroDefQuery - hir::db::ParseMacroExpansionQuery hir::db::MacroExpandQuery + hir::db::ExpandProcMacroQuery + hir::db::MacroExpandErrorQuery hir::db::HygieneFrameQuery - hir::db::InternMacroCallQuery // DefDatabase hir::db::FileItemTreeQuery - hir::db::BlockDefMapQuery hir::db::CrateDefMapQueryQuery - hir::db::FieldsAttrsQuery - hir::db::VariantsAttrsQuery - hir::db::FieldsAttrsSourceMapQuery - hir::db::VariantsAttrsSourceMapQuery + hir::db::BlockDefMapQuery hir::db::StructDataQuery + hir::db::StructDataWithDiagnosticsQuery hir::db::UnionDataQuery + hir::db::UnionDataWithDiagnosticsQuery hir::db::EnumDataQuery + hir::db::EnumDataWithDiagnosticsQuery hir::db::ImplDataQuery + hir::db::ImplDataWithDiagnosticsQuery hir::db::TraitDataQuery + hir::db::TraitDataWithDiagnosticsQuery + hir::db::TraitAliasDataQuery hir::db::TypeAliasDataQuery hir::db::FunctionDataQuery hir::db::ConstDataQuery hir::db::StaticDataQuery + hir::db::Macro2DataQuery + hir::db::MacroRulesDataQuery + hir::db::ProcMacroDataQuery hir::db::BodyWithSourceMapQuery hir::db::BodyQuery hir::db::ExprScopesQuery hir::db::GenericParamsQuery + hir::db::VariantsAttrsQuery + hir::db::FieldsAttrsQuery + hir::db::VariantsAttrsSourceMapQuery + hir::db::FieldsAttrsSourceMapQuery hir::db::AttrsQuery hir::db::CrateLangItemsQuery hir::db::LangItemQuery hir::db::ImportMapQuery + hir::db::FieldVisibilitiesQuery + hir::db::FunctionVisibilityQuery + hir::db::ConstVisibilityQuery + hir::db::CrateSupportsNoStdQuery // HirDatabase hir::db::InferQueryQuery + hir::db::MirBodyQuery + hir::db::BorrowckQuery hir::db::TyQuery hir::db::ValueTyQuery hir::db::ImplSelfTyQuery + hir::db::ConstParamTyQuery + hir::db::ConstEvalQuery + hir::db::ConstEvalDiscriminantQuery hir::db::ImplTraitQuery hir::db::FieldTypesQuery + hir::db::LayoutOfAdtQuery + hir::db::TargetDataLayoutQuery hir::db::CallableItemSignatureQuery + hir::db::ReturnTypeImplTraitsQuery hir::db::GenericPredicatesForParamQuery hir::db::GenericPredicatesQuery + hir::db::TraitEnvironmentQuery hir::db::GenericDefaultsQuery hir::db::InherentImplsInCrateQuery - hir::db::TraitEnvironmentQuery + hir::db::InherentImplsInBlockQuery + hir::db::IncoherentInherentImplCratesQuery hir::db::TraitImplsInCrateQuery + hir::db::TraitImplsInBlockQuery hir::db::TraitImplsInDepsQuery - hir::db::AssociatedTyDataQuery + hir::db::InternCallableDefQuery + hir::db::InternLifetimeParamIdQuery + hir::db::InternImplTraitIdQuery + hir::db::InternTypeOrConstParamIdQuery + hir::db::InternClosureQuery + hir::db::InternGeneratorQuery hir::db::AssociatedTyDataQuery hir::db::TraitDatumQuery hir::db::StructDatumQuery hir::db::ImplDatumQuery hir::db::FnDefDatumQuery - hir::db::ReturnTypeImplTraitsQuery - hir::db::InternCallableDefQuery - hir::db::InternTypeOrConstParamIdQuery - hir::db::InternImplTraitIdQuery - hir::db::InternClosureQuery + hir::db::FnDefVarianceQuery + hir::db::AdtVarianceQuery hir::db::AssociatedTyValueQuery hir::db::TraitSolveQueryQuery - hir::db::InternTypeOrConstParamIdQuery + hir::db::ProgramClausesForChalkEnvQuery // SymbolsDatabase crate::symbol_index::ModuleSymbolsQuery @@ -153,8 +181,14 @@ impl RootDatabase { hir::db::InternConstQuery hir::db::InternStaticQuery hir::db::InternTraitQuery + hir::db::InternTraitAliasQuery hir::db::InternTypeAliasQuery hir::db::InternImplQuery + hir::db::InternExternBlockQuery + hir::db::InternBlockQuery + hir::db::InternMacro2Query + hir::db::InternProcMacroQuery + hir::db::InternMacroRulesQuery ]; acc.sort_by_key(|it| std::cmp::Reverse(it.1)); diff --git a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs index ed7f04fd8..4071c490b 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/defs.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/defs.rs @@ -9,7 +9,8 @@ use arrayvec::ArrayVec; use hir::{ Adt, AsAssocItem, AssocItem, BuiltinAttr, BuiltinType, Const, Crate, DeriveHelper, Field, Function, GenericParam, HasVisibility, Impl, ItemInNs, Label, Local, Macro, Module, ModuleDef, - Name, PathResolution, Semantics, Static, ToolModule, Trait, TypeAlias, Variant, Visibility, + Name, PathResolution, Semantics, Static, ToolModule, Trait, TraitAlias, TypeAlias, Variant, + Visibility, }; use stdx::impl_from; use syntax::{ @@ -31,6 +32,7 @@ pub enum Definition { Const(Const), Static(Static), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), BuiltinType(BuiltinType), SelfType(Impl), @@ -64,6 +66,7 @@ impl Definition { Definition::Const(it) => it.module(db), Definition::Static(it) => it.module(db), Definition::Trait(it) => it.module(db), + Definition::TraitAlias(it) => it.module(db), Definition::TypeAlias(it) => it.module(db), Definition::Variant(it) => it.module(db), Definition::SelfType(it) => it.module(db), @@ -87,6 +90,7 @@ impl Definition { Definition::Const(it) => it.visibility(db), Definition::Static(it) => it.visibility(db), Definition::Trait(it) => it.visibility(db), + Definition::TraitAlias(it) => it.visibility(db), Definition::TypeAlias(it) => it.visibility(db), Definition::Variant(it) => it.visibility(db), Definition::BuiltinType(_) => Visibility::Public, @@ -113,6 +117,7 @@ impl Definition { Definition::Const(it) => it.name(db)?, Definition::Static(it) => it.name(db), Definition::Trait(it) => it.name(db), + Definition::TraitAlias(it) => it.name(db), Definition::TypeAlias(it) => it.name(db), Definition::BuiltinType(it) => it.name(), Definition::SelfType(_) => return None, @@ -300,6 +305,7 @@ impl NameClass { ast::Item::Module(it) => Definition::Module(sema.to_def(&it)?), ast::Item::Static(it) => Definition::Static(sema.to_def(&it)?), ast::Item::Trait(it) => Definition::Trait(sema.to_def(&it)?), + ast::Item::TraitAlias(it) => Definition::TraitAlias(sema.to_def(&it)?), ast::Item::TypeAlias(it) => Definition::TypeAlias(sema.to_def(&it)?), ast::Item::Enum(it) => Definition::Adt(hir::Adt::Enum(sema.to_def(&it)?)), ast::Item::Struct(it) => Definition::Adt(hir::Adt::Struct(sema.to_def(&it)?)), @@ -321,7 +327,7 @@ impl NameClass { let pat_parent = ident_pat.syntax().parent(); if let Some(record_pat_field) = pat_parent.and_then(ast::RecordPatField::cast) { if record_pat_field.name_ref().is_none() { - if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { + if let Some((field, _)) = sema.resolve_record_pat_field(&record_pat_field) { return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field, @@ -463,9 +469,12 @@ impl NameRefClass { match_ast! { match parent { ast::MethodCallExpr(method_call) => { - sema.resolve_method_call(&method_call) - .map(Definition::Function) - .map(NameRefClass::Definition) + sema.resolve_method_call_field_fallback(&method_call) + .map(|it| { + it.map_left(Definition::Function) + .map_right(Definition::Field) + .either(NameRefClass::Definition, NameRefClass::Definition) + }) }, ast::FieldExpr(field_expr) => { sema.resolve_field(&field_expr) @@ -474,6 +483,13 @@ impl NameRefClass { }, ast::RecordPatField(record_pat_field) => { sema.resolve_record_pat_field(&record_pat_field) + .map(|(field, ..)|field) + .map(Definition::Field) + .map(NameRefClass::Definition) + }, + ast::RecordExprField(record_expr_field) => { + sema.resolve_record_field(&record_expr_field) + .map(|(field, ..)|field) .map(Definition::Field) .map(NameRefClass::Definition) }, @@ -542,7 +558,7 @@ impl NameRefClass { } impl_from!( - Field, Module, Function, Adt, Variant, Const, Static, Trait, TypeAlias, BuiltinType, Local, + Field, Module, Function, Adt, Variant, Const, Static, Trait, TraitAlias, TypeAlias, BuiltinType, Local, GenericParam, Label, Macro for Definition ); @@ -599,6 +615,7 @@ impl From<ModuleDef> for Definition { ModuleDef::Const(it) => Definition::Const(it), ModuleDef::Static(it) => Definition::Static(it), ModuleDef::Trait(it) => Definition::Trait(it), + ModuleDef::TraitAlias(it) => Definition::TraitAlias(it), ModuleDef::TypeAlias(it) => Definition::TypeAlias(it), ModuleDef::Macro(it) => Definition::Macro(it), ModuleDef::BuiltinType(it) => Definition::BuiltinType(it), @@ -616,6 +633,7 @@ impl From<Definition> for Option<ItemInNs> { Definition::Const(it) => ModuleDef::Const(it), Definition::Static(it) => ModuleDef::Static(it), Definition::Trait(it) => ModuleDef::Trait(it), + Definition::TraitAlias(it) => ModuleDef::TraitAlias(it), Definition::TypeAlias(it) => ModuleDef::TypeAlias(it), Definition::BuiltinType(it) => ModuleDef::BuiltinType(it), _ => return None, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs index 6e56efe34..8e3b1eef1 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/helpers.rs @@ -2,8 +2,8 @@ use std::collections::VecDeque; -use base_db::FileId; -use hir::{ItemInNs, ModuleDef, Name, Semantics}; +use base_db::{FileId, SourceDatabaseExt}; +use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics}; use syntax::{ ast::{self, make}, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, @@ -103,3 +103,9 @@ pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool { false } } + +pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool { + let root_file = krate.root_file(db); + let source_root_id = db.file_source_root(root_file); + !db.source_root(source_root_id).is_library +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs index 994d48385..b26b0a908 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/imports/import_assets.rs @@ -528,7 +528,7 @@ fn trait_applicable_items( }, ) } else { - trait_candidate.receiver_ty.iterate_method_candidates( + trait_candidate.receiver_ty.iterate_method_candidates_with_traits( db, scope, &trait_candidates, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs index 156bbb634..b1df11bf9 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/lib.rs @@ -22,7 +22,7 @@ pub mod source_change; pub mod symbol_index; pub mod traits; pub mod ty_filter; -pub mod use_trivial_contructor; +pub mod use_trivial_constructor; pub mod imports { pub mod import_assets; @@ -50,7 +50,7 @@ use base_db::{ AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast, }; use hir::{ - db::{AstDatabase, DefDatabase, HirDatabase}, + db::{DefDatabase, ExpandDatabase, HirDatabase}, symbols::FileSymbolKind, }; use stdx::hash::NoHashHashSet; @@ -68,7 +68,7 @@ pub type FxIndexMap<K, V> = #[salsa::database( base_db::SourceDatabaseExtStorage, base_db::SourceDatabaseStorage, - hir::db::AstDatabaseStorage, + hir::db::ExpandDatabaseStorage, hir::db::DefDatabaseStorage, hir::db::HirDatabaseStorage, hir::db::InternDatabaseStorage, @@ -95,8 +95,8 @@ impl fmt::Debug for RootDatabase { } } -impl Upcast<dyn AstDatabase> for RootDatabase { - fn upcast(&self) -> &(dyn AstDatabase + 'static) { +impl Upcast<dyn ExpandDatabase> for RootDatabase { + fn upcast(&self) -> &(dyn ExpandDatabase + 'static) { &*self } } @@ -191,6 +191,7 @@ pub enum SymbolKind { Struct, ToolModule, Trait, + TraitAlias, TypeAlias, TypeParam, Union, @@ -221,6 +222,7 @@ impl From<FileSymbolKind> for SymbolKind { FileSymbolKind::Static => SymbolKind::Static, FileSymbolKind::Struct => SymbolKind::Struct, FileSymbolKind::Trait => SymbolKind::Trait, + FileSymbolKind::TraitAlias => SymbolKind::TraitAlias, FileSymbolKind::TypeAlias => SymbolKind::TypeAlias, FileSymbolKind::Union => SymbolKind::Union, } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs index 84d70b258..f710211c8 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/rename.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/rename.rs @@ -119,15 +119,9 @@ impl Definition { Definition::Const(it) => name_range(it, sema), Definition::Static(it) => name_range(it, sema), 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)?; @@ -301,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) } @@ -470,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/src/tools/rust-analyzer/crates/ide-db/src/search.rs b/src/tools/rust-analyzer/crates/ide-db/src/search.rs index c18a27f17..12f5e4e2a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/search.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/search.rs @@ -244,14 +244,14 @@ impl Definition { DefWithBody::Variant(v) => v.source(db).map(|src| src.syntax().cloned()), }; return match def { - Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), + Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)), None => SearchScope::single_file(file_id), }; } if let Definition::SelfType(impl_) = self { return match impl_.source(db).map(|src| src.syntax().cloned()) { - Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), + Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)), None => SearchScope::single_file(file_id), }; } @@ -261,13 +261,14 @@ impl Definition { hir::GenericDef::Function(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Adt(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Trait(it) => it.source(db).map(|src| src.syntax().cloned()), + hir::GenericDef::TraitAlias(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::TypeAlias(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Impl(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Variant(it) => it.source(db).map(|src| src.syntax().cloned()), hir::GenericDef::Const(it) => it.source(db).map(|src| src.syntax().cloned()), }; return match def { - Some(def) => SearchScope::file_range(def.as_ref().original_file_range(db)), + Some(def) => SearchScope::file_range(def.as_ref().original_file_range_full(db)), None => SearchScope::single_file(file_id), }; } @@ -318,10 +319,6 @@ impl Definition { sema, scope: None, include_self_kw_refs: None, - local_repr: match self { - Definition::Local(local) => Some(local.representative(sema.db)), - _ => None, - }, search_self_mod: false, } } @@ -336,9 +333,6 @@ pub struct FindUsages<'a> { assoc_item_container: Option<hir::AssocItemContainer>, /// whether to search for the `Self` type of the definition include_self_kw_refs: Option<hir::Type>, - /// the local representative for the local definition we are searching for - /// (this is required for finding all local declarations in a or-pattern) - local_repr: Option<hir::Local>, /// whether to search for the `self` module search_self_mod: bool, } @@ -643,19 +637,6 @@ impl<'a> FindUsages<'a> { sink: &mut dyn FnMut(FileId, FileReference) -> bool, ) -> bool { 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) - ) => - { - let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); - let reference = FileReference { - range, - name: ast::NameLike::NameRef(name_ref.clone()), - category: ReferenceCategory::new(&def, name_ref), - }; - sink(file_id, reference) - } Some(NameRefClass::Definition(def)) if self.def == def // is our def a trait assoc item? then we want to find all assoc items from trait impls of our trait @@ -700,14 +681,16 @@ impl<'a> FindUsages<'a> { } } Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { - let field = Definition::Field(field); let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); + + let field = Definition::Field(field); + let local = Definition::Local(local); let access = match self.def { 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)) => { - ReferenceCategory::new(&Definition::Local(local), name_ref) + Definition::Local(_) if local == self.def => { + ReferenceCategory::new(&local, name_ref) } _ => return false, }; @@ -751,21 +734,6 @@ impl<'a> FindUsages<'a> { }; sink(file_id, reference) } - 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 - ) { - let FileRange { file_id, range } = self.sema.original_range(name.syntax()); - let reference = FileReference { - range, - name: ast::NameLike::Name(name.clone()), - category: None, - }; - return sink(file_id, reference); - } - false - } Some(NameClass::Definition(def)) if def != self.def => { match (&self.assoc_item_container, self.def) { // for type aliases we always want to reference the trait def and all the trait impl counterparts diff --git a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs index 8e338061d..936354f29 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/source_change.rs @@ -83,6 +83,14 @@ impl From<NoHashHashMap<FileId, TextEdit>> for SourceChange { } } +impl FromIterator<(FileId, TextEdit)> for SourceChange { + fn from_iter<T: IntoIterator<Item = (FileId, TextEdit)>>(iter: T) -> Self { + let mut this = SourceChange::default(); + this.extend(iter); + this + } +} + pub struct SourceChangeBuilder { pub edit: TextEditBuilder, pub file_id: FileId, diff --git a/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs index 39431bed3..39431bed3 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_contructor.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/use_trivial_constructor.rs diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs index 10e637979..114face2d 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs @@ -7,10 +7,15 @@ pub(crate) fn break_outside_of_loop( ctx: &DiagnosticsContext<'_>, d: &hir::BreakOutsideOfLoop, ) -> Diagnostic { - let construct = if d.is_break { "break" } else { "continue" }; + let message = if d.bad_value_break { + "can't break with a value in this position".to_owned() + } else { + let construct = if d.is_break { "break" } else { "continue" }; + format!("{construct} outside of loop") + }; Diagnostic::new( "break-outside-of-loop", - format!("{construct} outside of loop"), + message, ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, ) } @@ -135,4 +140,18 @@ fn foo() { "#, ); } + + #[test] + fn value_break_in_for_loop() { + check_diagnostics( + r#" +fn test() { + for _ in [()] { + break 3; + // ^^^^^^^ error: can't break with a value in this position + } +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs new file mode 100644 index 000000000..d2f27664d --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/expected_function.rs @@ -0,0 +1,39 @@ +use hir::HirDisplay; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: expected-function +// +// This diagnostic is triggered if a call is made on something that is not callable. +pub(crate) fn expected_function( + ctx: &DiagnosticsContext<'_>, + d: &hir::ExpectedFunction, +) -> Diagnostic { + Diagnostic::new( + "expected-function", + format!("expected function, found {}", d.found.display(ctx.sema.db)), + ctx.sema.diagnostics_display_range(d.call.clone().map(|it| it.into())).range, + ) + .experimental() +} + +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn foo() { + let x = 3; + x(); + // ^^^ error: expected function, found i32 + ""(); + // ^^^^ error: expected function, found &str + foo(); +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs new file mode 100644 index 000000000..72af9ebfc --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incoherent_impl.rs @@ -0,0 +1,77 @@ +use hir::InFile; + +use crate::{Diagnostic, DiagnosticsContext, Severity}; + +// Diagnostic: incoherent-impl +// +// This diagnostic is triggered if the targe type of an impl is from a foreign crate. +pub(crate) fn incoherent_impl(ctx: &DiagnosticsContext<'_>, d: &hir::IncoherentImpl) -> Diagnostic { + Diagnostic::new( + "incoherent-impl", + format!("cannot define inherent `impl` for foreign type"), + ctx.sema.diagnostics_display_range(InFile::new(d.file_id, d.impl_.clone().into())).range, + ) + .severity(Severity::Error) +} + +#[cfg(test)] +mod change_case { + use crate::tests::check_diagnostics; + + #[test] + fn primitive() { + check_diagnostics( + r#" + impl bool {} +//^^^^^^^^^^^^ error: cannot define inherent `impl` for foreign type +"#, + ); + } + + #[test] + fn primitive_rustc_allow_incoherent_impl() { + check_diagnostics( + r#" +impl bool { + #[rustc_allow_incoherent_impl] + fn falsch(self) -> Self { false } +} +"#, + ); + } + + #[test] + fn rustc_allow_incoherent_impl() { + check_diagnostics( + r#" +//- /lib.rs crate:foo +#[rustc_has_incoherent_inherent_impls] +pub struct S; +//- /main.rs crate:main deps:foo +impl foo::S { + #[rustc_allow_incoherent_impl] + fn func(self) {} +} +"#, + ); + check_diagnostics( + r#" +//- /lib.rs crate:foo +pub struct S; +//- /main.rs crate:main deps:foo + impl foo::S { #[rustc_allow_incoherent_impl] fn func(self) {} } +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: cannot define inherent `impl` for foreign type +"#, + ); + check_diagnostics( + r#" +//- /lib.rs crate:foo +#[rustc_has_incoherent_inherent_impls] +pub struct S; +//- /main.rs crate:main deps:foo + impl foo::S { fn func(self) {} } +//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: cannot define inherent `impl` for foreign type +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs index 6a78c08d4..db88bf7b9 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/incorrect_case.rs @@ -1,4 +1,4 @@ -use hir::{db::AstDatabase, InFile}; +use hir::{db::ExpandDatabase, InFile}; use ide_db::{assists::Assist, defs::NameClass}; use syntax::AstNode; diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs index 43af4d4f1..5c4327ff9 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_fields.rs @@ -1,11 +1,11 @@ use either::Either; use hir::{ - db::{AstDatabase, HirDatabase}, + db::{ExpandDatabase, HirDatabase}, known, AssocItem, HirDisplay, InFile, Type, }; use ide_db::{ assists::Assist, famous_defs::FamousDefs, imports::import_assets::item_for_path_search, - source_change::SourceChange, use_trivial_contructor::use_trivial_constructor, FxHashMap, + source_change::SourceChange, use_trivial_constructor::use_trivial_constructor, FxHashMap, }; use stdx::format_to; use syntax::{ diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index c24430ce6..ac4463331 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -1,5 +1,3 @@ -use hir::InFile; - use crate::{Diagnostic, DiagnosticsContext}; // Diagnostic: missing-match-arm @@ -12,7 +10,7 @@ pub(crate) fn missing_match_arms( Diagnostic::new( "missing-match-arm", format!("missing match arm: {}", d.uncovered_patterns), - ctx.sema.diagnostics_display_range(InFile::new(d.file, d.match_expr.clone().into())).range, + ctx.sema.diagnostics_display_range(d.scrutinee_expr.clone().map(Into::into)).range, ) } @@ -1038,7 +1036,6 @@ fn main() { #[test] fn reference_patterns_in_fields() { cov_mark::check_count!(validate_match_bailed_out, 2); - check_diagnostics( r#" fn main() { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs index ea1ea5a21..eb32db250 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/missing_unsafe.rs @@ -1,4 +1,10 @@ -use crate::{Diagnostic, DiagnosticsContext}; +use hir::db::ExpandDatabase; +use ide_db::{assists::Assist, source_change::SourceChange}; +use syntax::{ast, SyntaxNode}; +use syntax::{match_ast, AstNode}; +use text_edit::TextEdit; + +use crate::{fix, Diagnostic, DiagnosticsContext}; // Diagnostic: missing-unsafe // @@ -9,11 +15,83 @@ pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsaf "this operation is unsafe and requires an unsafe function or block", ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, ) + .with_fixes(fixes(ctx, d)) +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Option<Vec<Assist>> { + // The fixit will not work correctly for macro expansions, so we don't offer it in that case. + if d.expr.file_id.is_macro() { + return None; + } + + let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; + let expr = d.expr.value.to_node(&root); + + let node_to_add_unsafe_block = pick_best_node_to_add_unsafe_block(&expr)?; + + let replacement = format!("unsafe {{ {} }}", node_to_add_unsafe_block.text()); + let edit = TextEdit::replace(node_to_add_unsafe_block.text_range(), replacement); + let source_change = + SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + Some(vec![fix("add_unsafe", "Add unsafe block", source_change, expr.syntax().text_range())]) +} + +// Pick the first ancestor expression of the unsafe `expr` that is not a +// receiver of a method call, a field access, the left-hand side of an +// assignment, or a reference. As all of those cases would incur a forced move +// if wrapped which might not be wanted. That is: +// - `unsafe_expr.foo` -> `unsafe { unsafe_expr.foo }` +// - `unsafe_expr.foo.bar` -> `unsafe { unsafe_expr.foo.bar }` +// - `unsafe_expr.foo()` -> `unsafe { unsafe_expr.foo() }` +// - `unsafe_expr.foo.bar()` -> `unsafe { unsafe_expr.foo.bar() }` +// - `unsafe_expr += 1` -> `unsafe { unsafe_expr += 1 }` +// - `&unsafe_expr` -> `unsafe { &unsafe_expr }` +// - `&&unsafe_expr` -> `unsafe { &&unsafe_expr }` +fn pick_best_node_to_add_unsafe_block(unsafe_expr: &ast::Expr) -> Option<SyntaxNode> { + // The `unsafe_expr` might be: + // - `ast::CallExpr`: call an unsafe function + // - `ast::MethodCallExpr`: call an unsafe method + // - `ast::PrefixExpr`: dereference a raw pointer + // - `ast::PathExpr`: access a static mut variable + for (node, parent) in + unsafe_expr.syntax().ancestors().zip(unsafe_expr.syntax().ancestors().skip(1)) + { + match_ast! { + match parent { + // If the `parent` is a `MethodCallExpr`, that means the `node` + // is the receiver of the method call, because only the receiver + // can be a direct child of a method call. The method name + // itself is not an expression but a `NameRef`, and an argument + // is a direct child of an `ArgList`. + ast::MethodCallExpr(_) => continue, + ast::FieldExpr(_) => continue, + ast::RefExpr(_) => continue, + ast::BinExpr(it) => { + // Check if the `node` is the left-hand side of an + // assignment, if so, we don't want to wrap it in an unsafe + // block, e.g. `unsafe_expr += 1` + let is_left_hand_side_of_assignment = { + if let Some(ast::BinaryOp::Assignment { .. }) = it.op_kind() { + it.lhs().map(|lhs| lhs.syntax().text_range().contains_range(node.text_range())).unwrap_or(false) + } else { + false + } + }; + if !is_left_hand_side_of_assignment { + return Some(node); + } + }, + _ => { return Some(node); } + + } + } + } + None } #[cfg(test)] mod tests { - use crate::tests::check_diagnostics; + use crate::tests::{check_diagnostics, check_fix, check_no_fix}; #[test] fn missing_unsafe_diagnostic_with_raw_ptr() { @@ -23,7 +101,7 @@ fn main() { let x = &5 as *const usize; unsafe { let y = *x; } let z = *x; -} //^^ error: this operation is unsafe and requires an unsafe function or block +} //^^💡 error: this operation is unsafe and requires an unsafe function or block "#, ) } @@ -48,9 +126,9 @@ unsafe fn unsafe_fn() { fn main() { unsafe_fn(); - //^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block HasUnsafe.unsafe_fn(); - //^^^^^^^^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block unsafe { unsafe_fn(); HasUnsafe.unsafe_fn(); @@ -72,7 +150,7 @@ static mut STATIC_MUT: Ty = Ty { a: 0 }; fn main() { let x = STATIC_MUT.a; - //^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block unsafe { let x = STATIC_MUT.a; } @@ -94,9 +172,298 @@ extern "rust-intrinsic" { fn main() { let _ = bitreverse(12); let _ = floorf32(12.0); - //^^^^^^^^^^^^^^ error: this operation is unsafe and requires an unsafe function or block + //^^^^^^^^^^^^^^💡 error: this operation is unsafe and requires an unsafe function or block } "#, ); } + + #[test] + fn add_unsafe_block_when_dereferencing_a_raw_pointer() { + check_fix( + r#" +fn main() { + let x = &5 as *const usize; + let z = *x$0; +} +"#, + r#" +fn main() { + let x = &5 as *const usize; + let z = unsafe { *x }; +} +"#, + ); + } + + #[test] + fn add_unsafe_block_when_calling_unsafe_function() { + check_fix( + r#" +unsafe fn func() { + let x = &5 as *const usize; + let z = *x; +} +fn main() { + func$0(); +} +"#, + r#" +unsafe fn func() { + let x = &5 as *const usize; + let z = *x; +} +fn main() { + unsafe { func() }; +} +"#, + ) + } + + #[test] + fn add_unsafe_block_when_calling_unsafe_method() { + check_fix( + r#" +struct S(usize); +impl S { + unsafe fn func(&self) { + let x = &self.0 as *const usize; + let z = *x; + } +} +fn main() { + let s = S(5); + s.func$0(); +} +"#, + r#" +struct S(usize); +impl S { + unsafe fn func(&self) { + let x = &self.0 as *const usize; + let z = *x; + } +} +fn main() { + let s = S(5); + unsafe { s.func() }; +} +"#, + ) + } + + #[test] + fn add_unsafe_block_when_accessing_mutable_static() { + check_fix( + r#" +struct Ty { + a: u8, +} + +static mut STATIC_MUT: Ty = Ty { a: 0 }; + +fn main() { + let x = STATIC_MUT$0.a; +} +"#, + r#" +struct Ty { + a: u8, +} + +static mut STATIC_MUT: Ty = Ty { a: 0 }; + +fn main() { + let x = unsafe { STATIC_MUT.a }; +} +"#, + ) + } + + #[test] + fn add_unsafe_block_when_calling_unsafe_intrinsic() { + check_fix( + r#" +extern "rust-intrinsic" { + pub fn floorf32(x: f32) -> f32; +} + +fn main() { + let _ = floorf32$0(12.0); +} +"#, + r#" +extern "rust-intrinsic" { + pub fn floorf32(x: f32) -> f32; +} + +fn main() { + let _ = unsafe { floorf32(12.0) }; +} +"#, + ) + } + + #[test] + fn unsafe_expr_as_a_receiver_of_a_method_call() { + check_fix( + r#" +unsafe fn foo() -> String { + "string".to_string() +} + +fn main() { + foo$0().len(); +} +"#, + r#" +unsafe fn foo() -> String { + "string".to_string() +} + +fn main() { + unsafe { foo().len() }; +} +"#, + ) + } + + #[test] + fn unsafe_expr_as_an_argument_of_a_method_call() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let mut v = vec![]; + v.push(STATIC_MUT$0); +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let mut v = vec![]; + v.push(unsafe { STATIC_MUT }); +} +"#, + ) + } + + #[test] + fn unsafe_expr_as_left_hand_side_of_assignment() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + STATIC_MUT$0 = 1; +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + unsafe { STATIC_MUT = 1 }; +} +"#, + ) + } + + #[test] + fn unsafe_expr_as_right_hand_side_of_assignment() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x; + x = STATIC_MUT$0; +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x; + x = unsafe { STATIC_MUT }; +} +"#, + ) + } + + #[test] + fn unsafe_expr_in_binary_plus() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = STATIC_MUT$0 + 1; +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = unsafe { STATIC_MUT } + 1; +} +"#, + ) + } + + #[test] + fn ref_to_unsafe_expr() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = &STATIC_MUT$0; +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = unsafe { &STATIC_MUT }; +} +"#, + ) + } + + #[test] + fn ref_ref_to_unsafe_expr() { + check_fix( + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = &&STATIC_MUT$0; +} +"#, + r#" +static mut STATIC_MUT: u8 = 0; + +fn main() { + let x = unsafe { &&STATIC_MUT }; +} +"#, + ) + } + + #[test] + fn unsafe_expr_in_macro_call() { + check_no_fix( + r#" +unsafe fn foo() -> u8 { + 0 +} + +fn main() { + let x = format!("foo: {}", foo$0()); +} + "#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs new file mode 100644 index 000000000..96470265d --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -0,0 +1,649 @@ +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 regression_14310() { + check_diagnostics( + r#" + fn clone(mut i: &!) -> ! { + //^^^^^ 💡 weak: variable does not need to be mutable + *i + } + "#, + ); + } + + #[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 initialization_is_not_mutation_in_loop() { + check_diagnostics( + r#" +fn main() { + let a; + loop { + let c @ ( + mut b, + //^^^^^ 💡 weak: variable does not need to be mutable + mut d + //^^^^^ 💡 weak: variable does not need to be mutable + ); + a = 1; + //^^^^^ 💡 error: cannot mutate immutable variable `a` + b = 1; + c = (2, 3); + d = 3; + } +} +"#, + ); + } + + #[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 or_pattern_no_terminator() { + check_diagnostics( + r#" +enum Foo { + A, B, C, D +} + +use Foo::*; + +fn f(inp: (Foo, Foo, Foo, Foo)) { + let ((A, B, _, x) | (B, C | D, x, _)) = inp else { + return; + }; + x = B; + //^^^^^ 💡 error: cannot mutate immutable variable `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/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs index 8da04e628..24c521ed1 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/no_such_field.rs @@ -1,4 +1,4 @@ -use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics}; +use hir::{db::ExpandDatabase, HasSource, HirDisplay, Semantics}; use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase}; use syntax::{ ast::{self, edit::IndentLevel, make}, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs index 0b3121c76..67da5c7f2 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_assoc_item.rs @@ -118,4 +118,42 @@ fn main(s: module::Struct) { "#, ); } + + #[test] + fn can_see_through_top_level_anonymous_const() { + // regression test for #14046. + check_diagnostics( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) fn method(self) {} + pub(crate) const A: usize = 42; + } + }; + mod inner { + const _: () = { + impl crate::S { + pub(crate) fn method2(self) {} + pub(crate) const B: usize = 42; + pub(super) fn private(self) {} + pub(super) const PRIVATE: usize = 42; + } + }; + } +} +fn main() { + S.method(); + S::A; + S.method2(); + S::B; + S.private(); + //^^^^^^^^^^^ error: function `private` is private + S::PRIVATE; + //^^^^^^^^^^ error: const `PRIVATE` is private +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs index e630ae368..be83ad6aa 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/private_field.rs @@ -65,4 +65,24 @@ fn main(s: module::Struct) { "#, ); } + + #[test] + fn block_module_madness() { + check_diagnostics( + r#" +fn main() { + let strukt = { + use crate as ForceParentBlockDefMap; + { + pub struct Struct { + field: (), + } + Struct { field: () } + } + }; + strukt.field; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs index 9826e1c70..9b1c65983 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs @@ -1,4 +1,4 @@ -use hir::{db::AstDatabase, InFile}; +use hir::{db::ExpandDatabase, InFile}; use ide_db::source_change::SourceChange; use syntax::{ ast::{self, HasArgList}, @@ -55,7 +55,18 @@ fn fixes( #[cfg(test)] mod tests { - use crate::tests::{check_diagnostics, check_fix}; + use crate::{ + tests::{check_diagnostics_with_config, check_fix}, + DiagnosticsConfig, + }; + + #[track_caller] + pub(crate) fn check_diagnostics(ra_fixture: &str) { + let mut config = DiagnosticsConfig::test_sample(); + config.disabled.insert("inactive-code".to_string()); + config.disabled.insert("unresolved-method".to_string()); + check_diagnostics_with_config(config, ra_fixture) + } #[test] fn replace_filter_map_next_with_find_map2() { diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs index 2adae165e..4abc25a28 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/type_mismatch.rs @@ -1,8 +1,9 @@ -use hir::{db::AstDatabase, HirDisplay, Type}; +use either::Either; +use hir::{db::ExpandDatabase, HirDisplay, InFile, Type}; use ide_db::{famous_defs::FamousDefs, source_change::SourceChange}; use syntax::{ ast::{self, BlockExpr, ExprStmt}, - AstNode, + AstNode, AstPtr, }; use text_edit::TextEdit; @@ -10,19 +11,23 @@ use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext} // Diagnostic: type-mismatch // -// This diagnostic is triggered when the type of an expression does not match +// This diagnostic is triggered when the type of an expression or pattern does not match // the expected type. pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic { - let display_range = adjusted_display_range::<ast::BlockExpr>( - ctx, - d.expr.clone().map(|it| it.into()), - &|block| { - let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); - cov_mark::hit!(type_mismatch_on_block); - Some(r_curly_range) - }, - ); - + let display_range = match &d.expr_or_pat { + Either::Left(expr) => adjusted_display_range::<ast::BlockExpr>( + ctx, + expr.clone().map(|it| it.into()), + &|block| { + let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range(); + cov_mark::hit!(type_mismatch_on_block); + Some(r_curly_range) + }, + ), + Either::Right(pat) => { + ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range + } + }; let mut diag = Diagnostic::new( "type-mismatch", format!( @@ -42,10 +47,15 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> { let mut fixes = Vec::new(); - add_reference(ctx, d, &mut fixes); - add_missing_ok_or_some(ctx, d, &mut fixes); - remove_semicolon(ctx, d, &mut fixes); - str_ref_to_owned(ctx, d, &mut fixes); + match &d.expr_or_pat { + Either::Left(expr_ptr) => { + add_reference(ctx, d, expr_ptr, &mut fixes); + add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes); + remove_semicolon(ctx, d, expr_ptr, &mut fixes); + str_ref_to_owned(ctx, d, expr_ptr, &mut fixes); + } + Either::Right(_pat_ptr) => {} + } if fixes.is_empty() { None @@ -57,9 +67,10 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assi fn add_reference( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile<AstPtr<ast::Expr>>, acc: &mut Vec<Assist>, ) -> Option<()> { - let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range; + let range = ctx.sema.diagnostics_display_range(expr_ptr.clone().map(|it| it.into())).range; let (_, mutability) = d.expected.as_reference()?; let actual_with_ref = Type::reference(&d.actual, mutability); @@ -71,7 +82,7 @@ fn add_reference( let edit = TextEdit::insert(range.start(), ampersands); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("add_reference_here", "Add reference here", source_change, range)); Some(()) } @@ -79,10 +90,11 @@ fn add_reference( fn add_missing_ok_or_some( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile<AstPtr<ast::Expr>>, acc: &mut Vec<Assist>, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let scope = ctx.sema.scope(expr.syntax())?; @@ -109,7 +121,7 @@ fn add_missing_ok_or_some( builder.insert(expr.syntax().text_range().start(), format!("{variant_name}(")); builder.insert(expr.syntax().text_range().end(), ")".to_string()); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish()); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), builder.finish()); let name = format!("Wrap in {variant_name}"); acc.push(fix("wrap_in_constructor", &name, source_change, expr_range)); Some(()) @@ -118,10 +130,11 @@ fn add_missing_ok_or_some( fn remove_semicolon( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile<AstPtr<ast::Expr>>, acc: &mut Vec<Assist>, ) -> Option<()> { - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); if !d.actual.is_unit() { return None; } @@ -136,7 +149,7 @@ fn remove_semicolon( let edit = TextEdit::delete(semicolon_range); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range)); Some(()) @@ -145,24 +158,26 @@ fn remove_semicolon( fn str_ref_to_owned( ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, + expr_ptr: &InFile<AstPtr<ast::Expr>>, acc: &mut Vec<Assist>, ) -> Option<()> { let expected = d.expected.display(ctx.sema.db); let actual = d.actual.display(ctx.sema.db); + // FIXME do this properly if expected.to_string() != "String" || actual.to_string() != "&str" { return None; } - let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; - let expr = d.expr.value.to_node(&root); + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); let expr_range = expr.syntax().text_range(); let to_owned = format!(".to_owned()"); let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned); let source_change = - SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); + SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit); acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range)); Some(()) @@ -595,4 +610,19 @@ fn f() -> i32 { "#, ); } + + #[test] + fn type_mismatch_pat_smoke_test() { + check_diagnostics( + r#" +fn f() { + let &() = &mut (); + match &() { + &9 => () + //^ error: expected (), found i32 + } +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs new file mode 100644 index 000000000..cefa74e52 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_field.rs @@ -0,0 +1,148 @@ +use hir::{db::ExpandDatabase, HirDisplay, InFile}; +use ide_db::{ + assists::{Assist, AssistId, AssistKind}, + base_db::FileRange, + label::Label, + source_change::SourceChange, +}; +use syntax::{ast, AstNode, AstPtr}; +use text_edit::TextEdit; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: unresolved-field +// +// This diagnostic is triggered if a field does not exist on a given type. +pub(crate) fn unresolved_field( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedField, +) -> Diagnostic { + let method_suffix = if d.method_with_same_name_exists { + ", but a method with a similar name exists" + } else { + "" + }; + Diagnostic::new( + "unresolved-field", + format!( + "no field `{}` on type `{}`{method_suffix}", + d.name, + d.receiver.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)) + .experimental() +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedField) -> Option<Vec<Assist>> { + if d.method_with_same_name_exists { + method_fix(ctx, &d.expr) + } else { + // FIXME: add quickfix + + None + } +} + +// FIXME: We should fill out the call here, mvoe the cursor and trigger signature help +fn method_fix( + ctx: &DiagnosticsContext<'_>, + expr_ptr: &InFile<AstPtr<ast::Expr>>, +) -> Option<Vec<Assist>> { + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); + let FileRange { range, file_id } = ctx.sema.original_range_opt(expr.syntax())?; + Some(vec![Assist { + id: AssistId("expected-field-found-method-call-fix", AssistKind::QuickFix), + label: Label::new("Use parentheses to call the method".to_string()), + group: None, + target: range, + source_change: Some(SourceChange::from_text_edit( + file_id, + TextEdit::insert(range.end(), "()".to_owned()), + )), + trigger_signature_help: false, + }]) +} +#[cfg(test)] +mod tests { + use crate::tests::check_diagnostics; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn main() { + ().foo; + // ^^^^^^ error: no field `foo` on type `()` +} +"#, + ); + } + + #[test] + fn method_clash() { + check_diagnostics( + r#" +struct Foo; +impl Foo { + fn bar(&self) {} +} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } + + #[test] + fn method_trait_() { + check_diagnostics( + r#" +struct Foo; +trait Bar { + fn bar(&self) {} +} +impl Bar for Foo {} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } + + #[test] + fn method_trait_2() { + check_diagnostics( + r#" +struct Foo; +trait Bar { + fn bar(&self); +} +impl Bar for Foo { + fn bar(&self) {} +} +fn foo() { + Foo.bar; + // ^^^^^^^ 💡 error: no field `bar` on type `Foo`, but a method with a similar name exists +} +"#, + ); + } + + #[test] + fn no_diagnostic_on_unknown() { + check_diagnostics( + r#" +fn foo() { + x.foo; + (&x).foo; + (&((x,),),).foo; +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs new file mode 100644 index 000000000..f3ec6efa7 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_method.rs @@ -0,0 +1,131 @@ +use hir::{db::ExpandDatabase, HirDisplay}; +use ide_db::{ + assists::{Assist, AssistId, AssistKind}, + base_db::FileRange, + label::Label, + source_change::SourceChange, +}; +use syntax::{ast, AstNode, TextRange}; +use text_edit::TextEdit; + +use crate::{Diagnostic, DiagnosticsContext}; + +// Diagnostic: unresolved-method +// +// This diagnostic is triggered if a method does not exist on a given type. +pub(crate) fn unresolved_method( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedMethodCall, +) -> Diagnostic { + let field_suffix = if d.field_with_same_name.is_some() { + ", but a field with a similar name exists" + } else { + "" + }; + Diagnostic::new( + "unresolved-method", + format!( + "no method `{}` on type `{}`{field_suffix}", + d.name, + d.receiver.display(ctx.sema.db) + ), + ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, + ) + .with_fixes(fixes(ctx, d)) + .experimental() +} + +fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> { + if let Some(ty) = &d.field_with_same_name { + field_fix(ctx, d, ty) + } else { + // FIXME: add quickfix + None + } +} + +fn field_fix( + ctx: &DiagnosticsContext<'_>, + d: &hir::UnresolvedMethodCall, + ty: &hir::Type, +) -> Option<Vec<Assist>> { + if !ty.impls_fnonce(ctx.sema.db) { + return None; + } + let expr_ptr = &d.expr; + let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?; + let expr = expr_ptr.value.to_node(&root); + let (file_id, range) = match expr { + ast::Expr::MethodCallExpr(mcall) => { + let FileRange { range, file_id } = + ctx.sema.original_range_opt(mcall.receiver()?.syntax())?; + let FileRange { range: range2, file_id: file_id2 } = + ctx.sema.original_range_opt(mcall.name_ref()?.syntax())?; + if file_id != file_id2 { + return None; + } + (file_id, TextRange::new(range.start(), range2.end())) + } + _ => return None, + }; + Some(vec![Assist { + id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix), + label: Label::new("Use parentheses to call the value of the field".to_string()), + group: None, + target: range, + source_change: Some(SourceChange::from_iter([ + (file_id, TextEdit::insert(range.start(), "(".to_owned())), + (file_id, TextEdit::insert(range.end(), ")".to_owned())), + ])), + trigger_signature_help: false, + }]) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_diagnostics, check_fix}; + + #[test] + fn smoke_test() { + check_diagnostics( + r#" +fn main() { + ().foo(); + // ^^^^^^^^ error: no method `foo` on type `()` +} +"#, + ); + } + + #[test] + fn field() { + check_diagnostics( + r#" +struct Foo { bar: i32 } +fn foo() { + Foo { bar: i32 }.bar(); + // ^^^^^^^^^^^^^^^^^^^^^^ error: no method `bar` on type `Foo`, but a field with a similar name exists +} +"#, + ); + } + + #[test] + fn callable_field() { + check_fix( + r#" +//- minicore: fn +struct Foo { bar: fn() } +fn foo() { + Foo { bar: foo }.b$0ar(); +} +"#, + r#" +struct Foo { bar: fn() } +fn foo() { + (Foo { bar: foo }.bar)(); +} +"#, + ); + } +} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs index 91395f1d8..94614f11c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_module.rs @@ -1,4 +1,4 @@ -use hir::db::AstDatabase; +use hir::db::ExpandDatabase; use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSystemEdit}; use itertools::Itertools; use syntax::AstNode; diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index 64ba08ac8..71f136b8c 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -27,7 +27,9 @@ mod handlers { pub(crate) mod break_outside_of_loop; + pub(crate) mod expected_function; pub(crate) mod inactive_code; + pub(crate) mod incoherent_impl; pub(crate) mod incorrect_case; pub(crate) mod invalid_derive_target; pub(crate) mod macro_error; @@ -36,6 +38,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; @@ -43,6 +46,8 @@ mod handlers { pub(crate) mod type_mismatch; pub(crate) mod unimplemented_builtin_macro; pub(crate) mod unresolved_extern_crate; + pub(crate) mod unresolved_field; + pub(crate) mod unresolved_method; pub(crate) mod unresolved_import; pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_module; @@ -248,7 +253,9 @@ pub fn diagnostics( #[rustfmt::skip] let d = match diag { AnyDiagnostic::BreakOutsideOfLoop(d) => handlers::break_outside_of_loop::break_outside_of_loop(&ctx, &d), + AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d), AnyDiagnostic::IncorrectCase(d) => handlers::incorrect_case::incorrect_case(&ctx, &d), + AnyDiagnostic::IncoherentImpl(d) => handlers::incoherent_impl::incoherent_impl(&ctx, &d), AnyDiagnostic::MacroError(d) => handlers::macro_error::macro_error(&ctx, &d), AnyDiagnostic::MalformedDerive(d) => handlers::malformed_derive::malformed_derive(&ctx, &d), AnyDiagnostic::MismatchedArgCount(d) => handlers::mismatched_arg_count::mismatched_arg_count(&ctx, &d), @@ -267,7 +274,10 @@ pub fn diagnostics( AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled), 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/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs index 57b5ab6ab..a8e883690 100644 --- a/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs +++ b/src/tools/rust-analyzer/crates/ide-ssr/src/matching.rs @@ -561,7 +561,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> { .sema .resolve_method_call_as_callable(code) .and_then(|callable| callable.receiver_param(self.sema.db)) - .map(|self_param| self_param.kind()) + .map(|(self_param, _)| self_param.kind()) .unwrap_or(ast::SelfParamKind::Owned); } } diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs index b4a7f2b91..fae25f310 100644 --- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs +++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs @@ -107,7 +107,18 @@ pub(crate) fn remove_links(markdown: &str) -> String { out } -/// Retrieve a link to documentation for the given symbol. +// Feature: Open Docs +// +// Retrieve a link to documentation for the given symbol. +// +// The simplest way to use this feature is via the context menu. Right-click on +// the selected item. The context menu opens. Select **Open Docs**. +// +// |=== +// | Editor | Action Name +// +// | VS Code | **rust-analyzer: Open Docs** +// |=== pub(crate) fn external_docs( db: &RootDatabase, position: &FilePosition, @@ -181,6 +192,7 @@ pub(crate) fn resolve_doc_path_for_def( Definition::Const(it) => it.resolve_doc_path(db, link, ns), Definition::Static(it) => it.resolve_doc_path(db, link, ns), Definition::Trait(it) => it.resolve_doc_path(db, link, ns), + Definition::TraitAlias(it) => it.resolve_doc_path(db, link, ns), Definition::TypeAlias(it) => it.resolve_doc_path(db, link, ns), Definition::Macro(it) => it.resolve_doc_path(db, link, ns), Definition::Field(it) => it.resolve_doc_path(db, link, ns), @@ -493,6 +505,7 @@ fn filename_and_frag_for_def( None => String::from("index.html"), }, Definition::Trait(t) => format!("trait.{}.html", t.name(db)), + Definition::TraitAlias(t) => format!("traitalias.{}.html", t.name(db)), Definition::TypeAlias(t) => format!("type.{}.html", t.name(db)), Definition::BuiltinType(t) => format!("primitive.{}.html", t.name()), Definition::Function(f) => format!("fn.{}.html", f.name(db)), diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs index b23763dce..a32ac3549 100644 --- a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs +++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs @@ -149,6 +149,7 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> { ast::Enum(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Enum)), ast::Variant(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Variant)), ast::Trait(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Trait)), + ast::TraitAlias(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::TraitAlias)), ast::Module(it) => decl(it, StructureNodeKind::SymbolKind(SymbolKind::Module)), ast::TypeAlias(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::TypeAlias)), ast::RecordField(it) => decl_with_type_ref(&it, it.ty(), StructureNodeKind::SymbolKind(SymbolKind::Field)), @@ -262,6 +263,8 @@ enum E { X, Y(i32) } type T = (); static S: i32 = 92; const C: i32 = 92; +trait Tr {} +trait Alias = Tr; impl E {} @@ -459,9 +462,31 @@ fn g() {} }, StructureNode { parent: None, + label: "Tr", + navigation_range: 239..241, + node_range: 233..244, + kind: SymbolKind( + Trait, + ), + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "Alias", + navigation_range: 251..256, + node_range: 245..262, + kind: SymbolKind( + TraitAlias, + ), + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, label: "impl E", - navigation_range: 239..240, - node_range: 234..243, + navigation_range: 269..270, + node_range: 264..273, kind: SymbolKind( Impl, ), @@ -471,8 +496,8 @@ fn g() {} StructureNode { parent: None, label: "impl fmt::Debug for E", - navigation_range: 265..266, - node_range: 245..269, + navigation_range: 295..296, + node_range: 275..299, kind: SymbolKind( Impl, ), @@ -482,8 +507,8 @@ fn g() {} StructureNode { parent: None, label: "mc", - navigation_range: 284..286, - node_range: 271..303, + navigation_range: 314..316, + node_range: 301..333, kind: SymbolKind( Macro, ), @@ -493,8 +518,8 @@ fn g() {} StructureNode { parent: None, label: "mcexp", - navigation_range: 334..339, - node_range: 305..356, + navigation_range: 364..369, + node_range: 335..386, kind: SymbolKind( Macro, ), @@ -504,8 +529,8 @@ fn g() {} StructureNode { parent: None, label: "mcexp", - navigation_range: 387..392, - node_range: 358..409, + navigation_range: 417..422, + node_range: 388..439, kind: SymbolKind( Macro, ), @@ -515,8 +540,8 @@ fn g() {} StructureNode { parent: None, label: "obsolete", - navigation_range: 428..436, - node_range: 411..441, + navigation_range: 458..466, + node_range: 441..471, kind: SymbolKind( Function, ), @@ -528,8 +553,8 @@ fn g() {} StructureNode { parent: None, label: "very_obsolete", - navigation_range: 481..494, - node_range: 443..499, + navigation_range: 511..524, + node_range: 473..529, kind: SymbolKind( Function, ), @@ -541,8 +566,8 @@ fn g() {} StructureNode { parent: None, label: "Some region name", - navigation_range: 501..528, - node_range: 501..528, + navigation_range: 531..558, + node_range: 531..558, kind: Region, detail: None, deprecated: false, @@ -550,8 +575,8 @@ fn g() {} StructureNode { parent: None, label: "m", - navigation_range: 568..569, - node_range: 543..606, + navigation_range: 598..599, + node_range: 573..636, kind: SymbolKind( Module, ), @@ -560,22 +585,22 @@ fn g() {} }, StructureNode { parent: Some( - 20, + 22, ), label: "dontpanic", - navigation_range: 543..563, - node_range: 543..563, + navigation_range: 573..593, + node_range: 573..593, kind: Region, detail: None, deprecated: false, }, StructureNode { parent: Some( - 20, + 22, ), label: "f", - navigation_range: 575..576, - node_range: 572..581, + navigation_range: 605..606, + node_range: 602..611, kind: SymbolKind( Function, ), @@ -586,11 +611,11 @@ fn g() {} }, StructureNode { parent: Some( - 20, + 22, ), label: "g", - navigation_range: 598..599, - node_range: 582..604, + navigation_range: 628..629, + node_range: 612..634, kind: SymbolKind( Function, ), diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs index 93019527f..cf0819a25 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs @@ -766,6 +766,13 @@ trait Foo$0 { } check( r#" +trait Foo$0 = ; + //^^^ +"#, + ); + + check( + r#" mod bar$0 { } //^^^ "#, @@ -1066,6 +1073,23 @@ fn f() -> impl Sub<Item$0 = u8> {} } #[test] + fn goto_def_for_module_declaration_in_path_if_types_and_values_same_name() { + check( + r#" +mod bar { + pub struct Foo {} + //^^^ + pub fn Foo() {} +} + +fn baz() { + let _foo_enum: bar::Foo$0 = bar::Foo {}; +} + "#, + ) + } + + #[test] fn unknown_assoc_ty() { check_unresolved( r#" @@ -1406,7 +1430,6 @@ include!("included.rs$0"); ); } - #[cfg(test)] mod goto_impl_of_trait_fn { use super::check; #[test] diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs index 190ab80ba..a1a119629 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs @@ -297,6 +297,7 @@ impl Foo<str> {} //- /lib.rs crate:main deps:core fn foo(_: bool$0) {{}} //- /libcore.rs crate:core +#![rustc_coherence_is_core] #[lang = "bool"] impl bool {} //^^^^ diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs index 55cdb3200..6d2d0bd63 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_type_definition.rs @@ -55,7 +55,7 @@ pub(crate) fn goto_type_definition( ty } else { let record_field = ast::RecordPatField::for_field_name_ref(&it)?; - sema.resolve_record_pat_field(&record_field)?.ty(db) + sema.resolve_record_pat_field(&record_field)?.1 } }, _ => return None, diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs index c889eb930..d88ffd25c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 5f2c61f5b..64b2221bd 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -30,6 +30,7 @@ pub struct HoverConfig { pub documentation: bool, pub keywords: bool, pub format: HoverDocFormat, + pub interpret_tests: bool, } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index 22611cfb8..da725ce50 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -3,7 +3,8 @@ use std::fmt::Display; use either::Either; use hir::{ - Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo, + db::DefDatabase, Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, + MirEvalError, Semantics, TypeInfo, }; use ide_db::{ base_db::SourceDatabase, @@ -402,7 +403,20 @@ pub(super) fn definition( )) }), Definition::Module(it) => label_and_docs(db, it), - Definition::Function(it) => label_and_docs(db, it), + Definition::Function(it) => label_and_layout_info_and_docs(db, it, |_| { + if !config.interpret_tests { + return None; + } + match it.eval(db) { + Ok(()) => Some("pass".into()), + Err(MirEvalError::Panic) => Some("fail".into()), + Err(MirEvalError::MirLowerError(f, e)) => { + let name = &db.function_data(f).name; + Some(format!("error: fail to lower {name} due {e:?}")) + } + Err(e) => Some(format!("error: {e:?}")), + } + }), Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| { let layout = it.layout(db).ok()?; Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes())) @@ -410,7 +424,7 @@ pub(super) fn definition( Definition::Variant(it) => label_value_and_docs(db, it, |&it| { if !it.parent_enum(db).is_data_carrying(db) { match it.eval(db) { - Ok(x) => Some(format!("{x}")), + Ok(x) => Some(if x >= 10 { format!("{x} ({x:#X})") } else { format!("{x}") }), Err(_) => it.value(db).map(|x| format!("{x:?}")), } } else { @@ -418,9 +432,9 @@ pub(super) fn definition( } }), Definition::Const(it) => label_value_and_docs(db, it, |it| { - let body = it.eval(db); + let body = it.render_eval(db); match body { - Ok(x) => Some(format!("{x}")), + Ok(x) => Some(x), Err(_) => { let source = it.source(db)?; let mut body = source.value.body()?.syntax().clone(); @@ -440,6 +454,7 @@ pub(super) fn definition( Some(body.to_string()) }), Definition::Trait(it) => label_and_docs(db, it), + Definition::TraitAlias(it) => label_and_docs(db, it), Definition::TypeAlias(it) => label_and_docs(db, it), Definition::BuiltinType(it) => { return famous_defs @@ -620,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() @@ -634,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/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index bd7ce2f1d..57bf0f9ad 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -4,16 +4,19 @@ use syntax::TextRange; use crate::{fixture, HoverConfig, HoverDocFormat}; +const HOVER_BASE_CONFIG: HoverConfig = HoverConfig { + links_in_hover: false, + documentation: true, + format: HoverDocFormat::Markdown, + keywords: true, + interpret_tests: false, +}; + fn check_hover_no_result(ra_fixture: &str) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap(); @@ -25,12 +28,7 @@ fn check(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap() @@ -47,12 +45,7 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HOVER_BASE_CONFIG, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) .unwrap() @@ -71,9 +64,8 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) { .hover( &HoverConfig { links_in_hover: true, - documentation: true, - keywords: true, format: HoverDocFormat::PlainText, + ..HOVER_BASE_CONFIG }, FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) }, ) @@ -91,12 +83,7 @@ fn check_actions(ra_fixture: &str, expect: Expect) { let (analysis, file_id, position) = fixture::range_or_position(ra_fixture); let hover = analysis .hover( - &HoverConfig { - links_in_hover: true, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, + &HoverConfig { links_in_hover: true, ..HOVER_BASE_CONFIG }, FileRange { file_id, range: position.range_or_empty() }, ) .unwrap() @@ -106,34 +93,13 @@ fn check_actions(ra_fixture: &str, expect: Expect) { fn check_hover_range(ra_fixture: &str, expect: Expect) { let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, - range, - ) - .unwrap() - .unwrap(); + let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap().unwrap(); expect.assert_eq(hover.info.markup.as_str()) } fn check_hover_range_no_results(ra_fixture: &str) { let (analysis, range) = fixture::range(ra_fixture); - let hover = analysis - .hover( - &HoverConfig { - links_in_hover: false, - documentation: true, - keywords: true, - format: HoverDocFormat::Markdown, - }, - range, - ) - .unwrap(); + let hover = analysis.hover(&HOVER_BASE_CONFIG, range).unwrap(); assert!(hover.is_none()); } @@ -490,7 +456,6 @@ fn hover_field_offset() { // Hovering over the field when instantiating check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 } "#, expect![[r#" @@ -512,7 +477,6 @@ fn hover_shows_struct_field_info() { // Hovering over the field when instantiating check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a: u32 } fn main() { @@ -535,7 +499,6 @@ fn main() { // Hovering over the field in the definition check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 struct Foo { field_a$0: u32 } fn main() { @@ -611,6 +574,27 @@ const foo$0: u32 = { } #[test] +fn hover_eval_complex_constants() { + check( + r#" + struct X { f1: (), f2: i32 } + const foo$0: (i8, X, i64) = (1, X { f2: 5 - 1, f1: () }, 1 - 2); + "#, + expect![[r#" + *foo* + + ```rust + test + ``` + + ```rust + const foo: (i8, X, i64) = (1, X { f1: (), f2: 4 }, -1) + ``` + "#]], + ); +} + +#[test] fn hover_default_generic_types() { check( r#" @@ -1467,8 +1451,6 @@ fn my() {} fn test_hover_struct_doc_comment() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - /// This is an example /// multiline doc /// @@ -1527,7 +1509,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar + struct Bar // size = 0, align = 1 ``` --- @@ -1556,7 +1538,7 @@ fn foo() { let bar = Ba$0r; } ``` ```rust - struct Bar + struct Bar // size = 0, align = 1 ``` --- @@ -1584,7 +1566,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar + pub struct Bar // size = 0, align = 1 ``` --- @@ -1611,7 +1593,7 @@ pub struct B$0ar ``` ```rust - pub struct Bar + pub struct Bar // size = 0, align = 1 ``` --- @@ -2913,8 +2895,6 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); } fn hover_field_pat_shorthand_ref_match_ergonomics() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - struct S { f: i32, } @@ -3506,8 +3486,8 @@ impl<const LEN: usize> Foo<LEN$0> {} } #[test] -fn hover_const_eval_variant() { - // show hex for <10 +fn hover_const_eval_discriminant() { + // Don't show hex for <10 check( r#" #[repr(u8)] @@ -3532,7 +3512,7 @@ enum E { This is a doc "#]], ); - // show hex for >10 + // Show hex for >10 check( r#" #[repr(u8)] @@ -3656,7 +3636,7 @@ trait T { } impl T for i32 { const AA: A = A { - i: 2 + i: 2 + 3 } } fn main() { @@ -3671,9 +3651,7 @@ fn main() { ``` ```rust - const AA: A = A { - i: 2 - } + const AA: A = A { i: 5 } ``` "#]], ); @@ -3792,7 +3770,6 @@ const FOO$0: usize = 1 << 3; This is a doc "#]], ); - // show hex for >10 check( r#" /// This is a doc @@ -3850,7 +3827,7 @@ const FOO$0: i32 = 2 - 3; ``` ```rust - const FOO: i32 = -1 + const FOO: i32 = -1 (0xFFFFFFFF) ``` --- @@ -4011,6 +3988,28 @@ const FOO$0: f32 = 1f32; This is a doc "#]], ); + // Don't show `<ref-not-supported>` in const hover + check( + r#" +/// This is a doc +const FOO$0: &i32 = &2; +"#, + expect![[r#" + *FOO* + + ```rust + test + ``` + + ```rust + const FOO: &i32 = &2 + ``` + + --- + + This is a doc + "#]], + ); //show f64 typecasted from float check( r#" @@ -4354,8 +4353,6 @@ fn main() { fn hover_intra_doc_links() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - pub mod theitem { /// This is the item. Cool! pub struct TheItem; @@ -4496,7 +4493,7 @@ trait A where fn string_shadowed_with_inner_items() { check( r#" -//- /main.rs crate:main deps:alloc target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 +//- /main.rs crate:main deps:alloc /// Custom `String` type. struct String; @@ -5191,7 +5188,7 @@ foo_macro!( ``` ```rust - pub struct Foo + pub struct Foo // size = 0, align = 1 ``` --- @@ -5205,8 +5202,6 @@ foo_macro!( fn hover_intra_in_attr() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - #[doc = "Doc comment for [`Foo$0`]"] pub struct Foo(i32); "#, @@ -5295,7 +5290,7 @@ pub struct Type; ``` ```rust - const KONST: dep::Type = $crate::Type + const KONST: dep::Type = Type ``` "#]], ); @@ -5327,8 +5322,6 @@ enum Enum { fn hover_record_variant_field() { check( r#" -//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128 - enum Enum { RecordV { field$0: u32 } } @@ -5647,3 +5640,204 @@ fn main() { "#]], ); } + +#[test] +fn assoc_fn_in_block_local_impl() { + check( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) fn foo() {} + } + }; +} +fn test() { + S::foo$0(); +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(crate) fn foo() + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + const _: () = { + const _: () = { + impl crate::S { + pub(crate) fn foo() {} + } + }; + }; +} +fn test() { + S::foo$0(); +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(crate) fn foo() + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + mod inner { + const _: () = { + impl crate::S { + pub(super) fn foo() {} + } + }; + } + + fn test() { + crate::S::foo$0(); + } +} +"#, + expect![[r#" + *foo* + + ```rust + test::S + ``` + + ```rust + pub(super) fn foo() + ``` + "#]], + ); +} + +#[test] +fn assoc_const_in_block_local_impl() { + check( + r#" +struct S; +mod m { + const _: () = { + impl crate::S { + pub(crate) const A: () = (); + } + }; +} +fn test() { + S::A$0; +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(crate) const A: () = () + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + const _: () = { + const _: () = { + impl crate::S { + pub(crate) const A: () = (); + } + }; + }; +} +fn test() { + S::A$0; +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(crate) const A: () = () + ``` + "#]], + ); + + check( + r#" +struct S; +mod m { + mod inner { + const _: () = { + impl crate::S { + pub(super) const A: () = (); + } + }; + } + + fn test() { + crate::S::A$0; + } +} +"#, + expect![[r#" + *A* + + ```rust + test + ``` + + ```rust + pub(super) const A: () = () + ``` + "#]], + ); +} + +#[test] +fn field_as_method_call_fallback() { + check( + r#" +struct S { f: u32 } +fn test() { + S { f: 0 }.f$0(); +} +"#, + expect![[r#" + *f* + + ```rust + test::S + ``` + + ```rust + f: u32 // size = 4, align = 4, offset = 0 + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs index 188eb7f97..46505b304 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs @@ -31,19 +31,31 @@ pub(super) fn hints( return None; } - // These inherit from the inner expression which would result in duplicate hints - if let ast::Expr::ParenExpr(_) - | ast::Expr::IfExpr(_) - | ast::Expr::BlockExpr(_) - | ast::Expr::MatchExpr(_) = expr - { + // ParenExpr resolve to their contained expressions HIR so they will dupe these hints + if let ast::Expr::ParenExpr(_) = expr { return None; } + if let ast::Expr::BlockExpr(b) = expr { + if !b.is_standalone() { + return None; + } + } let descended = sema.descend_node_into_attributes(expr.clone()).pop(); let desc_expr = descended.as_ref().unwrap_or(expr); let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?; + if let ast::Expr::BlockExpr(_) | ast::Expr::IfExpr(_) | ast::Expr::MatchExpr(_) = desc_expr { + if let [Adjustment { kind: Adjust::Deref(_), source, .. }, Adjustment { kind: Adjust::Borrow(_), source: _, target }] = + &*adjustments + { + // Don't show unnecessary reborrows for these, they will just repeat the inner ones again + if source == target { + return None; + } + } + } + let (postfix, needs_outer_parens, needs_inner_parens) = mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode); @@ -67,6 +79,7 @@ pub(super) fn hints( for Adjustment { source, target, kind } in iter { if source == target { + cov_mark::hit!(same_type_adjustment); continue; } @@ -251,7 +264,7 @@ mod tests { check_with_config( InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, r#" -//- minicore: coerce_unsized, fn +//- minicore: coerce_unsized, fn, eq fn main() { let _: u32 = loop {}; //^^^^^^^<never-to-any> @@ -332,7 +345,7 @@ fn main() { loop {} //^^^^^^^<never-to-any> }; - let _: &mut [u32] = match () { () => &mut [] } + let _: &mut [u32] = match () { () => &mut [] }; //^^^^^^^<unsize> //^^^^^^^&mut $ //^^^^^^^* @@ -341,6 +354,12 @@ fn main() { //^^^^^^^^^^<unsize> //^^^^^^^^^^&mut $ //^^^^^^^^^^* + () == (); + // ^^& + // ^^& + (()) == {()}; + // ^^& + // ^^^^& } #[derive(Copy, Clone)] @@ -363,7 +382,7 @@ impl Struct { ..DISABLED_CONFIG }, r#" -//- minicore: coerce_unsized, fn +//- minicore: coerce_unsized, fn, eq fn main() { Struct.consume(); @@ -419,7 +438,7 @@ fn main() { loop {} //^^^^^^^.<never-to-any> }; - let _: &mut [u32] = match () { () => &mut [] } + let _: &mut [u32] = match () { () => &mut [] }; //^^^^^^^( //^^^^^^^) //^^^^^^^.* @@ -432,6 +451,12 @@ fn main() { //^^^^^^^^^^.* //^^^^^^^^^^.&mut //^^^^^^^^^^.<unsize> + () == (); + // ^^.& + // ^^.& + (()) == {()}; + // ^^.& + // ^^^^.& } #[derive(Copy, Clone)] @@ -499,6 +524,7 @@ fn main() { #[test] fn never_to_never_is_never_shown() { + cov_mark::check!(same_type_adjustment); check_with_config( InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, r#" @@ -606,14 +632,13 @@ fn a() { } #[test] - fn bug() { + fn let_stmt_explicit_ty() { check_with_config( InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG }, r#" fn main() { - // These should be identical, but they are not... - let () = return; + //^^^^^^<never-to-any> let (): () = return; //^^^^^^<never-to-any> } diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs index 4af7f9bdb..6a5092733 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs @@ -176,15 +176,12 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir mod tests { // This module also contains tests for super::closure_ret - use expect_test::expect; use syntax::{TextRange, TextSize}; use test_utils::extract_annotations; use crate::{fixture, inlay_hints::InlayHintsConfig}; - use crate::inlay_hints::tests::{ - check, check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG, - }; + use crate::inlay_hints::tests::{check, check_with_config, DISABLED_CONFIG, TEST_CONFIG}; use crate::ClosureReturnTypeHints; #[track_caller] @@ -278,8 +275,7 @@ fn main() { #[test] fn iterator_hint_regression_issue_12674() { // Ensure we don't crash while solving the projection type of iterators. - check_expect( - InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, + let (analysis, file_id) = fixture::file( r#" //- minicore: iterators struct S<T>(T); @@ -302,107 +298,18 @@ impl<'a, T> Iterator for SliceIter<'a, T> { fn main(a: SliceIter<'_, Container>) { a - .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v)))) - .map(|e| e); + .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v)))) + .map(|e| e); } - "#, - expect![[r#" - [ - InlayHint { - range: 484..554, - kind: Chaining, - label: [ - "impl ", - InlayHintLabelPart { - text: "Iterator", - linked_location: Some( - FileRange { - file_id: FileId( - 1, - ), - range: 2611..2619, - }, - ), - tooltip: "", - }, - "<", - InlayHintLabelPart { - text: "Item", - linked_location: Some( - FileRange { - file_id: FileId( - 1, - ), - range: 2643..2647, - }, - ), - tooltip: "", - }, - " = impl ", - InlayHintLabelPart { - text: "Iterator", - linked_location: Some( - FileRange { - file_id: FileId( - 1, - ), - range: 2611..2619, - }, - ), - tooltip: "", - }, - "<", - InlayHintLabelPart { - text: "Item", - linked_location: Some( - FileRange { - file_id: FileId( - 1, - ), - range: 2643..2647, - }, - ), - tooltip: "", - }, - " = &&str>>", - ], - }, - InlayHint { - range: 484..485, - kind: Chaining, - label: [ - "", - InlayHintLabelPart { - text: "SliceIter", - linked_location: Some( - FileRange { - file_id: FileId( - 0, - ), - range: 289..298, - }, - ), - tooltip: "", - }, - "<", - InlayHintLabelPart { - text: "Container", - linked_location: Some( - FileRange { - file_id: FileId( - 0, - ), - range: 238..247, - }, - ), - tooltip: "", - }, - ">", - ], - }, - ] - "#]], +"#, ); + analysis + .inlay_hints( + &InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, + file_id, + None, + ) + .unwrap(); } #[test] diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs index 0c54f084c..1e1771259 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs @@ -435,7 +435,7 @@ fn main() { file_id: FileId( 1, ), - range: 2611..2619, + range: 3415..3423, }, ), tooltip: "", @@ -448,7 +448,7 @@ fn main() { file_id: FileId( 1, ), - range: 2643..2647, + range: 3447..3451, }, ), tooltip: "", @@ -468,7 +468,7 @@ fn main() { file_id: FileId( 1, ), - range: 2611..2619, + range: 3415..3423, }, ), tooltip: "", @@ -481,7 +481,7 @@ fn main() { file_id: FileId( 1, ), - range: 2643..2647, + range: 3447..3451, }, ), tooltip: "", @@ -501,7 +501,7 @@ fn main() { file_id: FileId( 1, ), - range: 2611..2619, + range: 3415..3423, }, ), tooltip: "", @@ -514,7 +514,7 @@ fn main() { file_id: FileId( 1, ), - range: 2643..2647, + range: 3447..3451, }, ), tooltip: "", diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs index 5dd51ad11..67eaa553a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs +++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs @@ -59,8 +59,14 @@ fn variant_hints( }, kind: InlayKind::Discriminant, label: InlayHintLabel::simple( - match &d { - Ok(v) => format!("{}", v), + match d { + Ok(x) => { + if x >= 10 { + format!("{x} ({x:#X})") + } else { + format!("{x}") + } + } Err(_) => "?".into(), }, Some(InlayTooltip::String(match &d { diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index f2b535bdc..078b66dd3 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide/src/markup.rs b/src/tools/rust-analyzer/crates/ide/src/markup.rs index de9fef61a..411eb695f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/markup.rs +++ b/src/tools/rust-analyzer/crates/ide/src/markup.rs @@ -32,7 +32,7 @@ impl Markup { pub fn as_str(&self) -> &str { self.text.as_str() } - pub fn fenced_block(contents: &impl fmt::Display) -> Markup { + pub fn fenced_block(contents: impl fmt::Display) -> Markup { format!("```rust\n{contents}\n```").into() } } diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs index af5e96d23..349e79ecf 100644 --- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs +++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs @@ -208,6 +208,9 @@ pub(crate) fn def_to_moniker( Definition::Trait(trait_) => { MonikerDescriptor { name: trait_.name(db), desc: MonikerDescriptorKind::Type } } + Definition::TraitAlias(ta) => { + MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::Type } + } Definition::TypeAlias(ta) => { MonikerDescriptor { name: ta.name(db), desc: MonikerDescriptorKind::TypeParameter } } diff --git a/src/tools/rust-analyzer/crates/ide/src/move_item.rs b/src/tools/rust-analyzer/crates/ide/src/move_item.rs index ffc4bdd7d..b955ea99f 100644 --- a/src/tools/rust-analyzer/crates/ide/src/move_item.rs +++ b/src/tools/rust-analyzer/crates/ide/src/move_item.rs @@ -73,6 +73,7 @@ fn find_ancestors(item: SyntaxElement, direction: Direction, range: TextRange) - SyntaxKind::MACRO_CALL, SyntaxKind::TYPE_ALIAS, SyntaxKind::TRAIT, + SyntaxKind::TRAIT_ALIAS, SyntaxKind::IMPL, SyntaxKind::MACRO_DEF, SyntaxKind::STRUCT, diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs index 3aa799d43..6aae82f98 100644 --- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs +++ b/src/tools/rust-analyzer/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}, @@ -192,6 +192,7 @@ impl TryToNav for Definition { Definition::Const(it) => it.try_to_nav(db), Definition::Static(it) => it.try_to_nav(db), Definition::Trait(it) => it.try_to_nav(db), + Definition::TraitAlias(it) => it.try_to_nav(db), Definition::TypeAlias(it) => it.try_to_nav(db), Definition::BuiltinType(_) => None, Definition::ToolModule(_) => None, @@ -212,6 +213,7 @@ impl TryToNav for hir::ModuleDef { hir::ModuleDef::Const(it) => it.try_to_nav(db), hir::ModuleDef::Static(it) => it.try_to_nav(db), hir::ModuleDef::Trait(it) => it.try_to_nav(db), + hir::ModuleDef::TraitAlias(it) => it.try_to_nav(db), hir::ModuleDef::TypeAlias(it) => it.try_to_nav(db), hir::ModuleDef::Macro(it) => it.try_to_nav(db), hir::ModuleDef::BuiltinType(_) => None, @@ -249,6 +251,9 @@ impl ToNavFromAst for hir::TypeAlias { impl ToNavFromAst for hir::Trait { const KIND: SymbolKind = SymbolKind::Trait; } +impl ToNavFromAst for hir::TraitAlias { + const KIND: SymbolKind = SymbolKind::TraitAlias; +} impl<D> TryToNav for D where @@ -382,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()), @@ -393,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 @@ -414,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); @@ -544,6 +557,7 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> ast::Struct(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Enum(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Trait(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), + ast::TraitAlias(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Module(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::TypeAlias(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), ast::Const(it) => sema.to_def(&it).map(|it| it.display(db).to_string()), diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs index cabbc2872..3684c1033 100644 --- a/src/tools/rust-analyzer/crates/ide/src/references.rs +++ b/src/tools/rust-analyzer/crates/ide/src/references.rs @@ -1356,6 +1356,38 @@ impl Foo { } #[test] + fn test_trait_alias() { + check( + r#" +trait Foo {} +trait Bar$0 = Foo where Self: ; +fn foo<T: Bar>(_: impl Bar, _: &dyn Bar) {} +"#, + expect![[r#" + Bar TraitAlias FileId(0) 13..42 19..22 + + FileId(0) 53..56 + FileId(0) 66..69 + FileId(0) 79..82 + "#]], + ); + } + + #[test] + fn test_trait_alias_self() { + check( + r#" +trait Foo = where Self$0: ; +"#, + expect![[r#" + Self TypeParam FileId(0) 6..9 6..9 + + FileId(0) 18..22 + "#]], + ); + } + + #[test] fn test_attr_differs_from_fn_with_same_name() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs index c0237e1ed..e10c46381 100644 --- a/src/tools/rust-analyzer/crates/ide/src/rename.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs index 5b35262aa..8a8a9151c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs +++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs @@ -2,7 +2,7 @@ use std::fmt; use ast::HasName; use cfg::CfgExpr; -use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; +use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; use ide_assists::utils::test_related_attribute; use ide_db::{ base_db::{FilePosition, FileRange}, @@ -195,14 +195,13 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { // // Provides a sneak peek of all tests where the current item is used. // -// The simplest way to use this feature is via the context menu: -// - Right-click on the selected item. The context menu opens. -// - Select **Peek related tests** +// The simplest way to use this feature is via the context menu. Right-click on +// the selected item. The context menu opens. Select **Peek Related Tests**. // // |=== // | Editor | Action Name // -// | VS Code | **rust-analyzer: Peek related tests** +// | VS Code | **rust-analyzer: Peek Related Tests** // |=== pub(crate) fn related_tests( db: &RootDatabase, @@ -371,9 +370,9 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.generic_parameters(sema.db).peekable(); let params = if ty_args.peek().is_some() { - format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty.display(sema.db)))) + format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { String::new() }; @@ -417,6 +416,7 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> { Definition::Const(it) => it.attrs(db), Definition::Static(it) => it.attrs(db), Definition::Trait(it) => it.attrs(db), + Definition::TraitAlias(it) => it.attrs(db), Definition::TypeAlias(it) => it.attrs(db), Definition::Macro(it) => it.attrs(db), Definition::SelfType(it) => it.attrs(db), @@ -437,14 +437,10 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.generic_parameters(db).peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { - format_to!( - path, - "<{}>", - ty_args.format_with(",", |ty, cb| cb(&ty.display(db))) - ); + format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); } format_to!(path, "::{}", def_name); path.retain(|c| c != ' '); @@ -1001,6 +997,221 @@ impl Data { } #[test] + fn test_runnables_doc_test_in_impl_with_lifetime() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a>; +impl Data<'a> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 52..106, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_and_types() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, U>; +impl<T, U> Data<'a, T, U> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 70..124, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,U>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<const N: usize>; +impl<const N: usize> Data<N> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 79..133, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<N>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_types_and_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, const N: usize>; +impl<'a, T, const N: usize> Data<'a, T, N> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 100..154, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,N>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + #[test] fn test_runnables_module() { check( r#" @@ -2063,6 +2274,59 @@ mod tests { } #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_type_const_value() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, A, const B: usize, C, const D: u32>; +impl<A, C, const D: u32> Data<'a, A, 12, C, D> { + /// ``` + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 121..156, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,A,12,C,D>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] fn doc_test_type_params() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs index f70ca55a5..4b2c139f6 100644 --- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs +++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs @@ -16,7 +16,7 @@ use stdx::format_to; use syntax::{ algo, ast::{self, HasArgList}, - match_ast, AstNode, Direction, SyntaxToken, TextRange, TextSize, + match_ast, AstNode, Direction, SyntaxElementChildren, SyntaxToken, TextRange, TextSize, }; use crate::RootDatabase; @@ -102,6 +102,20 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio } return signature_help_for_record_lit(&sema, record, token); }, + ast::RecordPat(record) => { + let cursor_outside = record.record_pat_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token); + if cursor_outside { + continue; + } + return signature_help_for_record_pat(&sema, record, token); + }, + ast::TupleStructPat(tuple_pat) => { + let cursor_outside = tuple_pat.r_paren_token().as_ref() == Some(&token); + if cursor_outside { + continue; + } + return signature_help_for_tuple_struct_pat(&sema, tuple_pat, token); + }, _ => (), } } @@ -172,7 +186,7 @@ fn signature_help_for_call( res.signature.push('('); { - if let Some(self_param) = callable.receiver_param(db) { + if let Some((self_param, _)) = callable.receiver_param(db) { format_to!(res.signature, "{}", self_param) } let mut buf = String::new(); @@ -252,6 +266,10 @@ fn signature_help_for_generics( res.doc = it.docs(db).map(|it| it.into()); format_to!(res.signature, "trait {}", it.name(db)); } + hir::GenericDef::TraitAlias(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "trait {}", it.name(db)); + } hir::GenericDef::TypeAlias(it) => { res.doc = it.docs(db).map(|it| it.into()); format_to!(res.signature, "type {}", it.name(db)); @@ -342,11 +360,112 @@ fn signature_help_for_record_lit( record: ast::RecordExpr, token: SyntaxToken, ) -> Option<SignatureHelp> { - let active_parameter = record - .record_expr_field_list()? + signature_help_for_record_( + sema, + record.record_expr_field_list()?.syntax().children_with_tokens(), + &record.path()?, + record + .record_expr_field_list()? + .fields() + .filter_map(|field| sema.resolve_record_field(&field)) + .map(|(field, _, ty)| (field, ty)), + token, + ) +} + +fn signature_help_for_record_pat( + sema: &Semantics<'_, RootDatabase>, + record: ast::RecordPat, + token: SyntaxToken, +) -> Option<SignatureHelp> { + signature_help_for_record_( + sema, + record.record_pat_field_list()?.syntax().children_with_tokens(), + &record.path()?, + record + .record_pat_field_list()? + .fields() + .filter_map(|field| sema.resolve_record_pat_field(&field)), + token, + ) +} + +fn signature_help_for_tuple_struct_pat( + sema: &Semantics<'_, RootDatabase>, + pat: ast::TupleStructPat, + token: SyntaxToken, +) -> Option<SignatureHelp> { + let rest_pat = pat.fields().find(|it| matches!(it, ast::Pat::RestPat(_))); + let is_left_of_rest_pat = + rest_pat.map_or(true, |it| token.text_range().start() < it.syntax().text_range().end()); + + let mut res = SignatureHelp { + doc: None, + signature: String::new(), + parameters: vec![], + active_parameter: None, + }; + + let db = sema.db; + let path_res = sema.resolve_path(&pat.path()?)?; + let fields: Vec<_> = if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { + let en = variant.parent_enum(db); + + res.doc = en.docs(db).map(|it| it.into()); + format_to!(res.signature, "enum {}::{} (", en.name(db), variant.name(db)); + variant.fields(db) + } else { + let adt = match path_res { + PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?, + PathResolution::Def(ModuleDef::Adt(adt)) => adt, + _ => return None, + }; + + match adt { + hir::Adt::Struct(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "struct {} (", it.name(db)); + it.fields(db) + } + _ => return None, + } + }; + let commas = pat .syntax() .children_with_tokens() .filter_map(syntax::NodeOrToken::into_token) + .filter(|t| t.kind() == syntax::T![,]); + res.active_parameter = Some(if is_left_of_rest_pat { + commas.take_while(|t| t.text_range().start() <= token.text_range().start()).count() + } else { + let n_commas = commas + .collect::<Vec<_>>() + .into_iter() + .rev() + .take_while(|t| t.text_range().start() > token.text_range().start()) + .count(); + fields.len().saturating_sub(1).saturating_sub(n_commas) + }); + + let mut buf = String::new(); + for ty in fields.into_iter().map(|it| it.ty(db)) { + format_to!(buf, "{}", ty.display_truncated(db, Some(20))); + res.push_call_param(&buf); + buf.clear(); + } + res.signature.push_str(")"); + Some(res) +} + +fn signature_help_for_record_( + sema: &Semantics<'_, RootDatabase>, + field_list_children: SyntaxElementChildren, + path: &ast::Path, + fields2: impl Iterator<Item = (hir::Field, hir::Type)>, + token: SyntaxToken, +) -> Option<SignatureHelp> { + let active_parameter = field_list_children + .filter_map(syntax::NodeOrToken::into_token) .filter(|t| t.kind() == syntax::T![,]) .take_while(|t| t.text_range().start() <= token.text_range().start()) .count(); @@ -361,7 +480,7 @@ fn signature_help_for_record_lit( let fields; let db = sema.db; - let path_res = sema.resolve_path(&record.path()?)?; + let path_res = sema.resolve_path(path)?; if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res { fields = variant.fields(db); let en = variant.parent_enum(db); @@ -393,8 +512,7 @@ fn signature_help_for_record_lit( let mut fields = fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>(); let mut buf = String::new(); - for field in record.record_expr_field_list()?.fields() { - let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue }; + for (field, ty) in fields2 { let name = field.name(db); format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20))); res.push_record_field(&buf); @@ -435,6 +553,7 @@ mod tests { (database, FilePosition { file_id, offset }) } + #[track_caller] fn check(ra_fixture: &str, expect: Expect) { let fixture = format!( r#" @@ -887,6 +1006,119 @@ fn main() { } #[test] + fn tuple_struct_pat() { + check( + r#" +/// A cool tuple struct +struct S(u32, i32); +fn main() { + let S(0, $0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32) + --- ^^^ + "#]], + ); + } + + #[test] + fn tuple_struct_pat_rest() { + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(0, .., $0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- --- --- ^^^ + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16, u8); +fn main() { + let S(0, .., $0, 0); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16, u8) + --- --- --- ^^^ -- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S($0, .., 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + ^^^ --- --- --- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16, u8); +fn main() { + let S(1, .., 1, $0, 2); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16, u8) + --- --- --- ^^^ -- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(1, $0.., 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- ^^^ --- --- + "#]], + ); + check( + r#" +/// A cool tuple struct +struct S(u32, i32, f32, u16); +fn main() { + let S(1, ..$0, 1); +} +"#, + expect![[r#" + A cool tuple struct + ------ + struct S (u32, i32, f32, u16) + --- ^^^ --- --- + "#]], + ); + } + + #[test] fn generic_struct() { check( r#" @@ -1547,6 +1779,29 @@ impl S { } #[test] + fn record_pat() { + check( + r#" +struct Strukt<T, U = ()> { + t: T, + u: U, + unit: (), +} +fn f() { + let Strukt { + u: 0, + $0 + } +} +"#, + expect![[r#" + struct Strukt { u: i32, t: T, unit: () } + ------ ^^^^ -------- + "#]], + ); + } + + #[test] fn test_enum_in_nested_method_in_lambda() { check( r#" diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs index 3f7f6885f..c97691b14 100644 --- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs +++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs @@ -139,6 +139,7 @@ impl StaticIndex<'_> { documentation: true, keywords: true, format: crate::HoverDocFormat::Markdown, + interpret_tests: false, }; let tokens = tokens.filter(|token| { matches!( diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs index 892e6a9bb..2111baad7 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs @@ -217,7 +217,9 @@ fn highlight_name_ref( // to anything when used. // We can fix this for derive attributes since derive helpers are recorded, but not for // general attributes. - None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR) => { + None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR) + && !sema.hir_file_for(name_ref.syntax()).is_derive_attr_pseudo_expansion(sema.db) => + { return HlTag::Symbol(SymbolKind::Attribute).into(); } None => return HlTag::UnresolvedReference.into(), @@ -410,6 +412,7 @@ fn highlight_def( h } Definition::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)), + Definition::TraitAlias(_) => Highlight::new(HlTag::Symbol(SymbolKind::TraitAlias)), Definition::TypeAlias(type_) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs index 9139528c7..3c4cfc781 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/inject.rs @@ -274,6 +274,7 @@ fn module_def_to_hl_tag(def: Definition) -> HlTag { Definition::Const(_) => SymbolKind::Const, Definition::Static(_) => SymbolKind::Static, Definition::Trait(_) => SymbolKind::Trait, + Definition::TraitAlias(_) => SymbolKind::TraitAlias, Definition::TypeAlias(_) => SymbolKind::TypeAlias, Definition::BuiltinType(_) => return HlTag::BuiltinType, Definition::Macro(_) => SymbolKind::Macro, diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs index 3949f1189..a81c4ee0c 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tags.rs @@ -150,6 +150,7 @@ impl HlTag { SymbolKind::Struct => "struct", SymbolKind::ToolModule => "tool_module", SymbolKind::Trait => "trait", + SymbolKind::TraitAlias => "trait_alias", SymbolKind::TypeAlias => "type_alias", SymbolKind::TypeParam => "type_param", SymbolKind::Union => "union", diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html index 1a4398814..567ab8ccc 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/test_data/highlight_attributes.html @@ -53,6 +53,6 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd <span class="comment">// This is another normal comment</span> <span class="comment documentation">/// This is another doc comment</span> <span class="comment">// This is another normal comment</span> -<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> +<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="attribute attribute default_library library">derive</span><span class="parenthesis attribute">(</span><span class="derive attribute default_library library">Copy</span><span class="comma attribute">,</span> <span class="unresolved_reference attribute">Unresolved</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span> <span class="comment">// The reason for these being here is to test AttrIds</span> <span class="keyword">struct</span> <span class="struct declaration">Foo</span><span class="semicolon">;</span></code></pre>
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs index fc9b5d3ba..ac9bd8e39 100644 --- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs @@ -34,7 +34,7 @@ fn attributes() { // This is another normal comment /// This is another doc comment // This is another normal comment -#[derive(Copy)] +#[derive(Copy, Unresolved)] // The reason for these being here is to test AttrIds struct Foo; "#, diff --git a/src/tools/rust-analyzer/crates/ide/src/view_mir.rs b/src/tools/rust-analyzer/crates/ide/src/view_mir.rs new file mode 100644 index 000000000..a36aba58b --- /dev/null +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/parser/src/grammar.rs b/src/tools/rust-analyzer/crates/parser/src/grammar.rs index 15ec9e167..15435a26c 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar.rs @@ -198,6 +198,10 @@ impl BlockLike { fn is_block(self) -> bool { self == BlockLike::Block } + + fn is_blocklike(kind: SyntaxKind) -> bool { + matches!(kind, BLOCK_EXPR | IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR) + } } const VISIBILITY_FIRST: TokenSet = TokenSet::new(&[T![pub], T![crate]]); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs index 4ecaa6e6a..c13a19437 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/attributes.rs @@ -43,7 +43,7 @@ pub(super) fn meta(p: &mut Parser<'_>) { match p.current() { T![=] => { p.bump(T![=]); - if !expressions::expr(p) { + if expressions::expr(p).is_none() { p.error("expected expression"); } } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs index 4b080102a..a884d8b6e 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions.rs @@ -16,9 +16,9 @@ pub(super) enum Semicolon { const EXPR_FIRST: TokenSet = LHS_FIRST; -pub(super) fn expr(p: &mut Parser<'_>) -> bool { +pub(super) fn expr(p: &mut Parser<'_>) -> Option<CompletedMarker> { let r = Restrictions { forbid_structs: false, prefer_stmt: false }; - expr_bp(p, None, r, 1).is_some() + expr_bp(p, None, r, 1).map(|(m, _)| m) } pub(super) fn expr_stmt( @@ -120,16 +120,27 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) { // fn f() { let x: i32; } types::ascription(p); } + + let mut expr_after_eq: Option<CompletedMarker> = None; if p.eat(T![=]) { // test let_stmt_init // fn f() { let x = 92; } - expressions::expr(p); + expr_after_eq = expressions::expr(p); } if p.at(T![else]) { + // test_err let_else_right_curly_brace + // fn func() { let Some(_) = {Some(1)} else { panic!("h") };} + if let Some(expr) = expr_after_eq { + if BlockLike::is_blocklike(expr.kind()) { + p.error( + "right curly brace `}` before `else` in a `let...else` statement not allowed", + ) + } + } + // test let_else // fn f() { let Some(x) = opt else { return }; } - let m = p.start(); p.bump(T![else]); block_expr(p); @@ -578,7 +589,14 @@ fn arg_list(p: &mut Parser<'_>) { // fn main() { // foo(#[attr] 92) // } - delimited(p, T!['('], T![')'], T![,], EXPR_FIRST.union(ATTRIBUTE_FIRST), expr); + delimited( + p, + T!['('], + T![')'], + T![,], + EXPR_FIRST.union(ATTRIBUTE_FIRST), + |p: &mut Parser<'_>| expr(p).is_some(), + ); m.complete(p, ARG_LIST); } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs index efc260383..d051dd268 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/expressions/atom.rs @@ -163,10 +163,8 @@ pub(super) fn atom_expr( return None; } }; - let blocklike = match done.kind() { - IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR => BlockLike::Block, - _ => BlockLike::NotBlock, - }; + let blocklike = + if BlockLike::is_blocklike(done.kind()) { BlockLike::Block } else { BlockLike::NotBlock }; Some((done, blocklike)) } @@ -188,7 +186,7 @@ fn tuple_expr(p: &mut Parser<'_>) -> CompletedMarker { // test tuple_attrs // const A: (i64, i64) = (1, #[cfg(test)] 2); - if !expr(p) { + if expr(p).is_none() { break; } @@ -221,7 +219,7 @@ fn array_expr(p: &mut Parser<'_>) -> CompletedMarker { // test array_attrs // const A: &[i64] = &[1, #[cfg(test)] 2]; - if !expr(p) { + if expr(p).is_none() { break; } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs index c982e2d56..a8a1ccb15 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/items/traits.rs @@ -20,7 +20,7 @@ pub(super) fn trait_(p: &mut Parser<'_>, m: Marker) { // trait Z<U> = where Self: T<U>; generic_params::opt_where_clause(p); p.expect(T![;]); - m.complete(p, TRAIT); + m.complete(p, TRAIT_ALIAS); return; } diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs index 1064ae997..26490aa97 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/paths.rs @@ -77,6 +77,9 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { // type X = <A as B>::Output; // fn foo() { <usize as Default>::default(); } if first && p.eat(T![<]) { + // test_err angled_path_without_qual + // type X = <()>; + // type Y = <A as B>; types::type_(p); if p.eat(T![as]) { if is_use_path_start(p) { @@ -86,6 +89,9 @@ fn path_segment(p: &mut Parser<'_>, mode: Mode, first: bool) { } } p.expect(T![>]); + if !p.at(T![::]) { + p.error("expected `::`"); + } } else { let empty = if first { p.eat(T![::]); diff --git a/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs b/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs index abcefffa2..5f4977886 100644 --- a/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs +++ b/src/tools/rust-analyzer/crates/parser/src/grammar/patterns.rs @@ -431,14 +431,15 @@ fn slice_pat(p: &mut Parser<'_>) -> CompletedMarker { fn pat_list(p: &mut Parser<'_>, ket: SyntaxKind) { while !p.at(EOF) && !p.at(ket) { - if !p.at_ts(PAT_TOP_FIRST) { - p.error("expected a pattern"); - break; - } - pattern_top(p); - if !p.at(ket) { - p.expect(T![,]); + if !p.at(T![,]) { + if p.at_ts(PAT_TOP_FIRST) { + p.error(format!("expected {:?}, got {:?}", T![,], p.current())); + } else { + break; + } + } else { + p.bump(T![,]); } } } diff --git a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs index 52b3fc23d..cd87b304a 100644 --- a/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs +++ b/src/tools/rust-analyzer/crates/parser/src/syntax_kind/generated.rs @@ -135,6 +135,7 @@ pub enum SyntaxKind { STATIC, CONST, TRAIT, + TRAIT_ALIAS, IMPL, TYPE_ALIAS, MACRO_CALL, diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rast new file mode 100644 index 000000000..026fecf4c --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rast @@ -0,0 +1,58 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + FOR_EXPR + FOR_KW "for" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + IN_KW "in" + WHITESPACE " " + RANGE_EXPR + LITERAL + INT_NUMBER "0" + DOT2 ".." + LITERAL + INT_NUMBER "10" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + RETURN_EXPR + RETURN_KW "return" + WHITESPACE "\n " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" +error 43: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rs new file mode 100644 index 000000000..d41027419 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0049_let_else_right_curly_brace_for.rs @@ -0,0 +1,6 @@ +fn f() { + let _ = for _ in 0..10 { + } else { + return + }; +}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rast new file mode 100644 index 000000000..102321954 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rast @@ -0,0 +1,46 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + LOOP_EXPR + LOOP_KW "loop" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + RETURN_EXPR + RETURN_KW "return" + WHITESPACE "\n " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" +error 33: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rs new file mode 100644 index 000000000..28b892869 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0050_let_else_right_curly_brace_loop.rs @@ -0,0 +1,6 @@ +fn f() { + let _ = loop { + } else { + return + }; +}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rast new file mode 100644 index 000000000..6e1181246 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rast @@ -0,0 +1,85 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + MATCH_EXPR + MATCH_KW "match" + WHITESPACE " " + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "Some" + ARG_LIST + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + MATCH_ARM_LIST + L_CURLY "{" + WHITESPACE "\n " + MATCH_ARM + TUPLE_STRUCT_PAT + PATH + PATH_SEGMENT + NAME_REF + IDENT "Some" + L_PAREN "(" + WILDCARD_PAT + UNDERSCORE "_" + R_PAREN ")" + WHITESPACE " " + FAT_ARROW "=>" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + COMMA "," + WHITESPACE "\n " + MATCH_ARM + IDENT_PAT + NAME + IDENT "None" + WHITESPACE " " + FAT_ARROW "=>" + WHITESPACE " " + LITERAL + INT_NUMBER "2" + COMMA "," + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + RETURN_EXPR + RETURN_KW "return" + WHITESPACE "\n " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" +error 83: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rs new file mode 100644 index 000000000..902d70dae --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0051_let_else_right_curly_brace_match.rs @@ -0,0 +1,8 @@ +fn f() { + let _ = match Some(1) { + Some(_) => 1, + None => 2, + } else { + return + }; +}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rast new file mode 100644 index 000000000..298d47d53 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rast @@ -0,0 +1,49 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + WHILE_EXPR + WHILE_KW "while" + WHITESPACE " " + LITERAL + TRUE_KW "true" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + RETURN_EXPR + RETURN_KW "return" + WHITESPACE "\n " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" +error 39: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rs new file mode 100644 index 000000000..a52343d8e --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0052_let_else_right_curly_brace_while.rs @@ -0,0 +1,6 @@ +fn f() { + let _ = while true { + } else { + return + }; +}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rast new file mode 100644 index 000000000..c0a4b0400 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rast @@ -0,0 +1,57 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + LET_STMT + LET_KW "let" + WHITESPACE " " + WILDCARD_PAT + UNDERSCORE "_" + WHITESPACE " " + EQ "=" + WHITESPACE " " + IF_EXPR + IF_KW "if" + WHITESPACE " " + LITERAL + TRUE_KW "true" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + RETURN_EXPR + RETURN_KW "return" + WHITESPACE "\n " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n" + R_CURLY "}" +error 49: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rs new file mode 100644 index 000000000..9a87aecbd --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/err/0053_let_else_right_curly_brace_if.rs @@ -0,0 +1,7 @@ +fn f() { + let _ = if true { + } else { + } else { + return + }; +}
\ No newline at end of file diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rast new file mode 100644 index 000000000..0529e9750 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rast @@ -0,0 +1,49 @@ +SOURCE_FILE + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "X" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + L_ANGLE "<" + TUPLE_TYPE + L_PAREN "(" + R_PAREN ")" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" + TYPE_ALIAS + TYPE_KW "type" + WHITESPACE " " + NAME + IDENT "Y" + WHITESPACE " " + EQ "=" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + L_ANGLE "<" + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "A" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH + PATH_SEGMENT + NAME_REF + IDENT "B" + R_ANGLE ">" + SEMICOLON ";" + WHITESPACE "\n" +error 13: expected `::` +error 32: expected `::` diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rs new file mode 100644 index 000000000..802d6cc14 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0016_angled_path_without_qual.rs @@ -0,0 +1,2 @@ +type X = <()>; +type Y = <A as B>; diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rast new file mode 100644 index 000000000..6ec580212 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rast @@ -0,0 +1,69 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "func" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + LET_STMT + LET_KW "let" + WHITESPACE " " + TUPLE_STRUCT_PAT + PATH + PATH_SEGMENT + NAME_REF + IDENT "Some" + L_PAREN "(" + WILDCARD_PAT + UNDERSCORE "_" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + CALL_EXPR + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "Some" + ARG_LIST + L_PAREN "(" + LITERAL + INT_NUMBER "1" + R_PAREN ")" + R_CURLY "}" + WHITESPACE " " + LET_ELSE + ELSE_KW "else" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE " " + MACRO_EXPR + MACRO_CALL + PATH + PATH_SEGMENT + NAME_REF + IDENT "panic" + BANG "!" + TOKEN_TREE + L_PAREN "(" + STRING "\"h\"" + R_PAREN ")" + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + R_CURLY "}" + WHITESPACE "\n" +error 35: right curly brace `}` before `else` in a `let...else` statement not allowed diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rs b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rs new file mode 100644 index 000000000..30d52fea3 --- /dev/null +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/err/0017_let_else_right_curly_brace.rs @@ -0,0 +1 @@ +fn func() { let Some(_) = {Some(1)} else { panic!("h") };} diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast index 2ef66484a..c45f87089 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0151_trait_alias.rast @@ -1,5 +1,5 @@ SOURCE_FILE - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME diff --git a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast index 4443d9d14..8f6782477 100644 --- a/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast +++ b/src/tools/rust-analyzer/crates/parser/test_data/parser/inline/ok/0177_trait_alias_where_clause.rast @@ -1,5 +1,5 @@ SOURCE_FILE - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME @@ -50,7 +50,7 @@ SOURCE_FILE IDENT "Copy" SEMICOLON ";" WHITESPACE "\n" - TRAIT + TRAIT_ALIAS TRAIT_KW "trait" WHITESPACE " " NAME diff --git a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs index 6550cf27e..4e5d640f1 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/build_scripts.rs @@ -15,13 +15,13 @@ use std::{ use cargo_metadata::{camino::Utf8Path, Message}; use la_arena::ArenaMap; -use paths::AbsPathBuf; +use paths::{AbsPath, AbsPathBuf}; use rustc_hash::FxHashMap; use semver::Version; use serde::Deserialize; use crate::{ - cfg_flag::CfgFlag, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, + cfg_flag::CfgFlag, utf8_stdout, CargoConfig, CargoFeatures, CargoWorkspace, InvocationLocation, InvocationStrategy, Package, }; @@ -67,6 +67,7 @@ impl WorkspaceBuildScripts { let mut cmd = Command::new(toolchain::cargo()); cmd.args(["check", "--quiet", "--workspace", "--message-format=json"]); + cmd.args(&config.extra_args); // --all-targets includes tests, benches and examples in addition to the // default lib and bins. This is an independent concept from the --target @@ -250,7 +251,7 @@ impl WorkspaceBuildScripts { if tracing::enabled!(tracing::Level::INFO) { for package in workspace.packages() { - let package_build_data = &mut outputs[package]; + let package_build_data = &outputs[package]; if !package_build_data.is_unchanged() { tracing::info!( "{}: {:?}", @@ -378,6 +379,84 @@ impl WorkspaceBuildScripts { pub(crate) fn get_output(&self, idx: Package) -> Option<&BuildScriptOutput> { self.outputs.get(idx) } + + pub(crate) fn rustc_crates( + rustc: &CargoWorkspace, + current_dir: &AbsPath, + extra_env: &FxHashMap<String, String>, + ) -> Self { + let mut bs = WorkspaceBuildScripts::default(); + for p in rustc.packages() { + bs.outputs.insert(p, BuildScriptOutput::default()); + } + let res = (|| { + let target_libdir = (|| { + let mut cargo_config = Command::new(toolchain::cargo()); + cargo_config.envs(extra_env); + cargo_config + .current_dir(current_dir) + .args(["rustc", "-Z", "unstable-options", "--print", "target-libdir"]) + .env("RUSTC_BOOTSTRAP", "1"); + if let Ok(it) = utf8_stdout(cargo_config) { + return Ok(it); + } + let mut cmd = Command::new(toolchain::rustc()); + cmd.envs(extra_env); + cmd.args(["--print", "target-libdir"]); + utf8_stdout(cmd) + })()?; + + let target_libdir = AbsPathBuf::try_from(PathBuf::from(target_libdir)) + .map_err(|_| anyhow::format_err!("target-libdir was not an absolute path"))?; + tracing::info!("Loading rustc proc-macro paths from {}", target_libdir.display()); + + let proc_macro_dylibs: Vec<(String, AbsPathBuf)> = std::fs::read_dir(target_libdir)? + .filter_map(|entry| { + let dir_entry = entry.ok()?; + if dir_entry.file_type().ok()?.is_file() { + let path = dir_entry.path(); + tracing::info!("p{:?}", path); + let extension = path.extension()?; + if extension == std::env::consts::DLL_EXTENSION { + let name = path.file_stem()?.to_str()?.split_once('-')?.0.to_owned(); + let path = AbsPathBuf::try_from(path).ok()?; + return Some((name, path)); + } + } + None + }) + .collect(); + for p in rustc.packages() { + let package = &rustc[p]; + if package.targets.iter().any(|&it| rustc[it].is_proc_macro) { + if let Some((_, path)) = proc_macro_dylibs + .iter() + .find(|(name, _)| *name.trim_start_matches("lib") == package.name) + { + bs.outputs[p].proc_macro_dylib_path = Some(path.clone()); + } + } + } + + if tracing::enabled!(tracing::Level::INFO) { + for package in rustc.packages() { + let package_build_data = &bs.outputs[package]; + if !package_build_data.is_unchanged() { + tracing::info!( + "{}: {:?}", + rustc[package].manifest.parent().display(), + package_build_data, + ); + } + } + } + Ok(()) + })(); + if let Err::<_, anyhow::Error>(e) = res { + bs.error = Some(e.to_string()); + } + bs + } } // FIXME: Find a better way to know if it is a dylib. diff --git a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs index fdc7859eb..01162b1a8 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/cargo_workspace.rs @@ -50,7 +50,7 @@ impl ops::Index<Target> for CargoWorkspace { /// Describes how to set the rustc source directory. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum RustcSource { +pub enum RustLibSource { /// Explicit path for the rustc source directory. Path(AbsPathBuf), /// Try to automatically detect where the rustc source directory is. @@ -95,16 +95,18 @@ pub struct CargoConfig { /// rustc target pub target: Option<String>, /// Sysroot loading behavior - pub sysroot: Option<RustcSource>, + pub sysroot: Option<RustLibSource>, pub sysroot_src: Option<AbsPathBuf>, /// rustc private crate source - pub rustc_source: Option<RustcSource>, + pub rustc_source: Option<RustLibSource>, /// crates to disable `#[cfg(test)]` on pub unset_test_crates: UnsetTestCrates, /// Invoke `cargo check` through the RUSTC_WRAPPER. pub wrap_rustc_in_build_scripts: bool, /// The command to run instead of `cargo check` for building build scripts. pub run_build_script_command: Option<Vec<String>>, + /// Extra args to pass to the cargo command. + pub extra_args: Vec<String>, /// Extra env vars to set when invoking the cargo command pub extra_env: FxHashMap<String, String>, pub invocation_strategy: InvocationStrategy, diff --git a/src/tools/rust-analyzer/crates/project-model/src/lib.rs b/src/tools/rust-analyzer/crates/project-model/src/lib.rs index 9b6a71db8..70cb71ae3 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/lib.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/lib.rs @@ -44,7 +44,7 @@ pub use crate::{ build_scripts::WorkspaceBuildScripts, cargo_workspace::{ CargoConfig, CargoFeatures, CargoWorkspace, Package, PackageData, PackageDependency, - RustcSource, Target, TargetData, TargetKind, UnsetTestCrates, + RustLibSource, Target, TargetData, TargetKind, UnsetTestCrates, }, manifest_path::ManifestPath, project_json::{ProjectJson, ProjectJsonData}, diff --git a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs index 328d2fbcf..74e41eda7 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/sysroot.rs @@ -88,23 +88,17 @@ impl Sysroot { } pub fn discover_with_src_override( - dir: &AbsPath, + current_dir: &AbsPath, extra_env: &FxHashMap<String, String>, src: AbsPathBuf, ) -> Result<Sysroot> { - tracing::debug!("discovering sysroot for {}", dir.display()); - let sysroot_dir = discover_sysroot_dir(dir, extra_env)?; + tracing::debug!("discovering sysroot for {}", current_dir.display()); + let sysroot_dir = discover_sysroot_dir(current_dir, extra_env)?; Ok(Sysroot::load(sysroot_dir, src)) } - pub fn discover_rustc( - cargo_toml: &ManifestPath, - extra_env: &FxHashMap<String, String>, - ) -> Option<ManifestPath> { - tracing::debug!("discovering rustc source for {}", cargo_toml.display()); - let current_dir = cargo_toml.parent(); - let sysroot_dir = discover_sysroot_dir(current_dir, extra_env).ok()?; - get_rustc_src(&sysroot_dir) + pub fn discover_rustc(&self) -> Option<ManifestPath> { + get_rustc_src(&self.root) } pub fn with_sysroot_dir(sysroot_dir: AbsPathBuf) -> Result<Sysroot> { @@ -282,4 +276,7 @@ unwind std_detect test"; -const PROC_MACRO_DEPS: &str = "std"; +// core is required for our builtin derives to work in the proc_macro lib currently +const PROC_MACRO_DEPS: &str = " +std +core"; diff --git a/src/tools/rust-analyzer/crates/project-model/src/tests.rs b/src/tools/rust-analyzer/crates/project-model/src/tests.rs index 9e9691d11..3754accbb 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/tests.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/tests.rs @@ -24,8 +24,8 @@ fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGr let project_workspace = ProjectWorkspace::Cargo { cargo: cargo_workspace, build_scripts: WorkspaceBuildScripts::default(), - sysroot: None, - rustc: None, + sysroot: Err(None), + rustc: Err(None), rustc_cfg: Vec::new(), cfg_overrides, toolchain: None, @@ -37,7 +37,7 @@ fn load_cargo_with_overrides(file: &str, cfg_overrides: CfgOverrides) -> CrateGr fn load_rust_project(file: &str) -> CrateGraph { let data = get_test_json_file(file); let project = rooted_project_json(data); - let sysroot = Some(get_fake_sysroot()); + let sysroot = Ok(get_fake_sysroot()); let project_workspace = ProjectWorkspace::Json { project, sysroot, rustc_cfg: Vec::new() }; to_crate_graph(project_workspace) } @@ -1547,6 +1547,15 @@ fn rust_project_hello_world_project_model() { ), prelude: true, }, + Dependency { + crate_id: CrateId( + 1, + ), + name: CrateName( + "core", + ), + prelude: true, + }, ], proc_macro: Err( "no proc macro loaded for sysroot crate", diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 2a11f1e8e..d1e53e12e 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -17,7 +17,7 @@ use stdx::{always, hash::NoHashHashMap}; use crate::{ build_scripts::BuildScriptOutput, - cargo_workspace::{DepKind, PackageData, RustcSource}, + cargo_workspace::{DepKind, PackageData, RustLibSource}, cfg_flag::CfgFlag, rustc_cfg, sysroot::SysrootCrate, @@ -69,8 +69,8 @@ pub enum ProjectWorkspace { Cargo { cargo: CargoWorkspace, build_scripts: WorkspaceBuildScripts, - sysroot: Option<Sysroot>, - rustc: Option<CargoWorkspace>, + sysroot: Result<Sysroot, Option<String>>, + rustc: Result<(CargoWorkspace, WorkspaceBuildScripts), Option<String>>, /// Holds cfg flags for the current target. We get those by running /// `rustc --print cfg`. /// @@ -82,7 +82,7 @@ pub enum ProjectWorkspace { target_layout: Result<String, String>, }, /// Project workspace was manually specified using a `rust-project.json` file. - Json { project: ProjectJson, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> }, + Json { project: ProjectJson, sysroot: Result<Sysroot, Option<String>>, rustc_cfg: Vec<CfgFlag> }, // FIXME: The primary limitation of this approach is that the set of detached files needs to be fixed at the beginning. // That's not the end user experience we should strive for. // Ideally, you should be able to just open a random detached file in existing cargo projects, and get the basic features working. @@ -93,7 +93,11 @@ pub enum ProjectWorkspace { // // /// Project with a set of disjoint files, not belonging to any particular workspace. /// Backed by basic sysroot crates for basic completion and highlighting. - DetachedFiles { files: Vec<AbsPathBuf>, sysroot: Option<Sysroot>, rustc_cfg: Vec<CfgFlag> }, + DetachedFiles { + files: Vec<AbsPathBuf>, + sysroot: Result<Sysroot, Option<String>>, + rustc_cfg: Vec<CfgFlag>, + }, } impl fmt::Debug for ProjectWorkspace { @@ -113,10 +117,10 @@ impl fmt::Debug for ProjectWorkspace { .debug_struct("Cargo") .field("root", &cargo.workspace_root().file_name()) .field("n_packages", &cargo.packages().len()) - .field("sysroot", &sysroot.is_some()) + .field("sysroot", &sysroot.is_ok()) .field( "n_rustc_compiler_crates", - &rustc.as_ref().map_or(0, |rc| rc.packages().len()), + &rustc.as_ref().map_or(0, |(rc, _)| rc.packages().len()), ) .field("n_rustc_cfg", &rustc_cfg.len()) .field("n_cfg_overrides", &cfg_overrides.len()) @@ -126,7 +130,7 @@ impl fmt::Debug for ProjectWorkspace { ProjectWorkspace::Json { project, sysroot, rustc_cfg } => { let mut debug_struct = f.debug_struct("Json"); debug_struct.field("n_crates", &project.n_crates()); - if let Some(sysroot) = sysroot { + if let Ok(sysroot) = sysroot { debug_struct.field("n_sysroot_crates", &sysroot.crates().len()); } debug_struct.field("n_rustc_cfg", &rustc_cfg.len()); @@ -135,7 +139,7 @@ impl fmt::Debug for ProjectWorkspace { ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f .debug_struct("DetachedFiles") .field("n_files", &files.len()) - .field("sysroot", &sysroot.is_some()) + .field("sysroot", &sysroot.is_ok()) .field("n_rustc_cfg", &rustc_cfg.len()) .finish(), } @@ -191,85 +195,81 @@ impl ProjectWorkspace { let cargo = CargoWorkspace::new(meta); let sysroot = match (&config.sysroot, &config.sysroot_src) { - (Some(RustcSource::Path(path)), None) => { - match Sysroot::with_sysroot_dir(path.clone()) { - Ok(it) => Some(it), - Err(e) => { - tracing::error!(%e, "Failed to find sysroot at {}.", path.display()); - None - } - } + (Some(RustLibSource::Path(path)), None) => { + Sysroot::with_sysroot_dir(path.clone()).map_err(|e| { + Some(format!("Failed to find sysroot at {}:{e}", path.display())) + }) } - (Some(RustcSource::Discover), None) => { - match Sysroot::discover(cargo_toml.parent(), &config.extra_env) { - Ok(it) => Some(it), - Err(e) => { - tracing::error!( - %e, - "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", - cargo_toml.display() - ); - None - } - } + (Some(RustLibSource::Discover), None) => { + Sysroot::discover(cargo_toml.parent(), &config.extra_env).map_err(|e| { + Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display())) + }) } - (Some(RustcSource::Path(sysroot)), Some(sysroot_src)) => { - Some(Sysroot::load(sysroot.clone(), sysroot_src.clone())) + (Some(RustLibSource::Path(sysroot)), Some(sysroot_src)) => { + Ok(Sysroot::load(sysroot.clone(), sysroot_src.clone())) } - (Some(RustcSource::Discover), Some(sysroot_src)) => { - match Sysroot::discover_with_src_override( + (Some(RustLibSource::Discover), Some(sysroot_src)) => { + Sysroot::discover_with_src_override( cargo_toml.parent(), &config.extra_env, sysroot_src.clone(), - ) { - Ok(it) => Some(it), - Err(e) => { - tracing::error!( - %e, - "Failed to find sysroot for Cargo.toml file {}. Is rust-src installed?", - cargo_toml.display() - ); - None - } - } + ).map_err(|e| { + Some(format!("Failed to find sysroot for Cargo.toml file {}. Is rust-src installed? {e}", cargo_toml.display())) + }) } - (None, _) => None, + (None, _) => Err(None), }; - if let Some(sysroot) = &sysroot { - tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); + if let Ok(sysroot) = &sysroot { + tracing::info!(workspace = %cargo_toml.display(), src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); } let rustc_dir = match &config.rustc_source { - Some(RustcSource::Path(path)) => ManifestPath::try_from(path.clone()).ok(), - Some(RustcSource::Discover) => { - Sysroot::discover_rustc(&cargo_toml, &config.extra_env) + Some(RustLibSource::Path(path)) => ManifestPath::try_from(path.clone()) + .map_err(|p| { + Some(format!("rustc source path is not absolute: {}", p.display())) + }), + Some(RustLibSource::Discover) => { + sysroot.as_ref().ok().and_then(Sysroot::discover_rustc).ok_or_else(|| { + Some(format!("Failed to discover rustc source for sysroot.")) + }) } - None => None, + None => Err(None), }; - if let Some(rustc_dir) = &rustc_dir { - tracing::info!(rustc_dir = %rustc_dir.display(), "Using rustc source"); - } - let rustc = match rustc_dir { - Some(rustc_dir) => match CargoWorkspace::fetch_metadata( + let rustc = rustc_dir.and_then(|rustc_dir| { + tracing::info!(workspace = %cargo_toml.display(), rustc_dir = %rustc_dir.display(), "Using rustc source"); + match CargoWorkspace::fetch_metadata( &rustc_dir, cargo_toml.parent(), - config, + &CargoConfig { + features: crate::CargoFeatures::default(), + ..config.clone() + }, progress, ) { - Ok(meta) => Some(CargoWorkspace::new(meta)), + Ok(meta) => { + let workspace = CargoWorkspace::new(meta); + let buildscripts = WorkspaceBuildScripts::rustc_crates( + &workspace, + cargo_toml.parent(), + &config.extra_env, + ); + Ok((workspace, buildscripts)) + } Err(e) => { tracing::error!( %e, "Failed to read Cargo metadata from rustc source at {}", rustc_dir.display() ); - None + Err(Some(format!( + "Failed to read Cargo metadata from rustc source at {}: {e}", + rustc_dir.display()) + )) } - }, - None => None, - }; + } + }); let rustc_cfg = rustc_cfg::get(Some(&cargo_toml), config.target.as_deref(), &config.extra_env); @@ -305,12 +305,12 @@ impl ProjectWorkspace { extra_env: &FxHashMap<String, String>, ) -> ProjectWorkspace { let sysroot = match (project_json.sysroot.clone(), project_json.sysroot_src.clone()) { - (Some(sysroot), Some(sysroot_src)) => Some(Sysroot::load(sysroot, sysroot_src)), + (Some(sysroot), Some(sysroot_src)) => Ok(Sysroot::load(sysroot, sysroot_src)), (Some(sysroot), None) => { // assume sysroot is structured like rustup's and guess `sysroot_src` let sysroot_src = sysroot.join("lib").join("rustlib").join("src").join("rust").join("library"); - Some(Sysroot::load(sysroot, sysroot_src)) + Ok(Sysroot::load(sysroot, sysroot_src)) } (None, Some(sysroot_src)) => { // assume sysroot is structured like rustup's and guess `sysroot` @@ -318,11 +318,11 @@ impl ProjectWorkspace { for _ in 0..5 { sysroot.pop(); } - Some(Sysroot::load(sysroot, sysroot_src)) + Ok(Sysroot::load(sysroot, sysroot_src)) } - (None, None) => None, + (None, None) => Err(None), }; - if let Some(sysroot) = &sysroot { + if let Ok(sysroot) = &sysroot { tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); } @@ -335,33 +335,23 @@ impl ProjectWorkspace { config: &CargoConfig, ) -> Result<ProjectWorkspace> { let sysroot = match &config.sysroot { - Some(RustcSource::Path(path)) => match Sysroot::with_sysroot_dir(path.clone()) { - Ok(it) => Some(it), - Err(e) => { - tracing::error!(%e, "Failed to find sysroot at {}.", path.display()); - None - } - }, - Some(RustcSource::Discover) => { + Some(RustLibSource::Path(path)) => Sysroot::with_sysroot_dir(path.clone()) + .map_err(|e| Some(format!("Failed to find sysroot at {}:{e}", path.display()))), + Some(RustLibSource::Discover) => { let dir = &detached_files .first() .and_then(|it| it.parent()) .ok_or_else(|| format_err!("No detached files to load"))?; - match Sysroot::discover(dir, &config.extra_env) { - Ok(it) => Some(it), - Err(e) => { - tracing::error!( - %e, - "Failed to find sysroot for {}. Is rust-src installed?", - dir.display() - ); - None - } - } + Sysroot::discover(dir, &config.extra_env).map_err(|e| { + Some(format!( + "Failed to find sysroot for {}. Is rust-src installed? {e}", + dir.display() + )) + }) } - None => None, + None => Err(None), }; - if let Some(sysroot) = &sysroot { + if let Ok(sysroot) = &sysroot { tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); } let rustc_cfg = rustc_cfg::get(None, None, &Default::default()); @@ -442,10 +432,18 @@ impl ProjectWorkspace { } } + pub fn workspace_definition_path(&self) -> Option<&AbsPath> { + match self { + ProjectWorkspace::Cargo { cargo, .. } => Some(cargo.workspace_root()), + ProjectWorkspace::Json { project, .. } => Some(project.path()), + ProjectWorkspace::DetachedFiles { .. } => None, + } + } + pub fn find_sysroot_proc_macro_srv(&self) -> Option<AbsPathBuf> { match self { - ProjectWorkspace::Cargo { sysroot: Some(sysroot), .. } - | ProjectWorkspace::Json { sysroot: Some(sysroot), .. } => { + ProjectWorkspace::Cargo { sysroot: Ok(sysroot), .. } + | ProjectWorkspace::Json { sysroot: Ok(sysroot), .. } => { let standalone_server_name = format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX); ["libexec", "lib"] @@ -461,7 +459,7 @@ impl ProjectWorkspace { /// The return type contains the path and whether or not /// the root is a member of the current workspace pub fn to_roots(&self) -> Vec<PackageRoot> { - let mk_sysroot = |sysroot: Option<&Sysroot>, project_root: Option<&AbsPath>| { + let mk_sysroot = |sysroot: Result<&Sysroot, _>, project_root: Option<&AbsPath>| { sysroot.map(|sysroot| PackageRoot { // mark the sysroot as mutable if it is located inside of the project is_local: project_root @@ -531,7 +529,7 @@ impl ProjectWorkspace { PackageRoot { is_local, include, exclude } }) .chain(mk_sysroot(sysroot.as_ref(), Some(cargo.workspace_root()))) - .chain(rustc.iter().flat_map(|rustc| { + .chain(rustc.iter().flat_map(|(rustc, _)| { rustc.packages().map(move |krate| PackageRoot { is_local: false, include: vec![rustc[krate].manifest.parent().to_path_buf()], @@ -559,7 +557,7 @@ impl ProjectWorkspace { sysroot_package_len + project.n_crates() } ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => { - let rustc_package_len = rustc.as_ref().map_or(0, |it| it.packages().len()); + let rustc_package_len = rustc.as_ref().map_or(0, |(it, _)| it.packages().len()); let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len()); cargo.packages().len() + sysroot_package_len + rustc_package_len } @@ -584,7 +582,7 @@ impl ProjectWorkspace { load_proc_macro, load, project, - sysroot.as_ref(), + sysroot.as_ref().ok(), extra_env, Err("rust-project.json projects have no target layout set".into()), ), @@ -600,9 +598,9 @@ impl ProjectWorkspace { } => cargo_to_crate_graph( load_proc_macro, load, - rustc, + rustc.as_ref().ok(), cargo, - sysroot.as_ref(), + sysroot.as_ref().ok(), rustc_cfg.clone(), cfg_overrides, build_scripts, @@ -616,7 +614,7 @@ impl ProjectWorkspace { rustc_cfg.clone(), load, files, - sysroot, + sysroot.as_ref().ok(), Err("detached file projects have no target layout set".into()), ) } @@ -778,7 +776,7 @@ fn project_json_to_crate_graph( fn cargo_to_crate_graph( load_proc_macro: &mut dyn FnMut(&str, &AbsPath) -> ProcMacroLoadResult, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, - rustc: &Option<CargoWorkspace>, + rustc: Option<&(CargoWorkspace, WorkspaceBuildScripts)>, cargo: &CargoWorkspace, sysroot: Option<&Sysroot>, rustc_cfg: Vec<CfgFlag>, @@ -924,7 +922,7 @@ fn cargo_to_crate_graph( if has_private { // If the user provided a path to rustc sources, we add all the rustc_private crates // and create dependencies on them for the crates which opt-in to that - if let Some(rustc_workspace) = rustc { + if let Some((rustc_workspace, rustc_build_scripts)) = rustc { handle_rustc_crates( &mut crate_graph, &mut pkg_to_lib_crate, @@ -937,7 +935,13 @@ fn cargo_to_crate_graph( &pkg_crates, &cfg_options, override_cfg, - build_scripts, + if rustc_workspace.workspace_root() == cargo.workspace_root() { + // the rustc workspace does not use the installed toolchain's proc-macro server + // so we need to make sure we don't use the pre compiled proc-macros there either + build_scripts + } else { + rustc_build_scripts + }, target_layout, ); } @@ -949,7 +953,7 @@ fn detached_files_to_crate_graph( rustc_cfg: Vec<CfgFlag>, load: &mut dyn FnMut(&AbsPath) -> Option<FileId>, detached_files: &[AbsPathBuf], - sysroot: &Option<Sysroot>, + sysroot: Option<&Sysroot>, target_layout: TargetLayoutLoadResult, ) -> CrateGraph { let _p = profile::span("detached_files_to_crate_graph"); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs index 93297faa6..6ce1de5d3 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -7,12 +7,12 @@ use std::{ }; use hir::{ - db::{AstDatabase, DefDatabase, HirDatabase}, + db::{DefDatabase, ExpandDatabase, HirDatabase}, AssocItem, Crate, Function, HasSource, HirDisplay, ModuleDef, }; use hir_def::{ body::{BodySourceMap, SyntheticSyntax}, - expr::ExprId, + expr::{ExprId, PatId}, FunctionId, }; use hir_ty::{Interner, TyExt, TypeFlags}; @@ -24,7 +24,7 @@ use ide_db::base_db::{ use itertools::Itertools; use oorandom::Rand32; use profile::{Bytes, StopWatch}; -use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustcSource}; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource}; use rayon::prelude::*; use rustc_hash::FxHashSet; use stdx::format_to; @@ -57,7 +57,7 @@ impl flags::AnalysisStats { let mut cargo_config = CargoConfig::default(); cargo_config.sysroot = match self.no_sysroot { true => None, - false => Some(RustcSource::Discover), + false => Some(RustLibSource::Discover), }; let no_progress = &|_| (); @@ -222,7 +222,11 @@ impl flags::AnalysisStats { let mut num_exprs = 0; let mut num_exprs_unknown = 0; let mut num_exprs_partially_unknown = 0; - let mut num_type_mismatches = 0; + let mut num_expr_type_mismatches = 0; + let mut num_pats = 0; + let mut num_pats_unknown = 0; + let mut num_pats_partially_unknown = 0; + let mut num_pat_type_mismatches = 0; let analysis = host.analysis(); for f in funcs.iter().copied() { let name = f.name(db); @@ -255,6 +259,8 @@ impl flags::AnalysisStats { let f_id = FunctionId::from(f); let (body, sm) = db.body_with_source_map(f_id.into()); let inference_result = db.infer(f_id.into()); + + // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = (num_exprs, num_exprs_unknown, num_exprs_partially_unknown); for (expr_id, _) in body.exprs.iter() { @@ -307,12 +313,12 @@ impl flags::AnalysisStats { if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, - location_csv(db, &analysis, vfs, &sm, expr_id), + location_csv_expr(db, &analysis, vfs, &sm, expr_id), ty.display(db) ); } if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { - num_type_mismatches += 1; + num_expr_type_mismatches += 1; if verbosity.is_verbose() { if let Some((path, start, end)) = expr_syntax_range(db, &analysis, vfs, &sm, expr_id) @@ -339,7 +345,7 @@ impl flags::AnalysisStats { if self.output == Some(OutputFormat::Csv) { println!( r#"{},mismatch,"{}","{}""#, - location_csv(db, &analysis, vfs, &sm, expr_id), + location_csv_expr(db, &analysis, vfs, &sm, expr_id), mismatch.expected.display(db), mismatch.actual.display(db) ); @@ -355,6 +361,109 @@ impl flags::AnalysisStats { num_exprs_partially_unknown - previous_partially_unknown )); } + // endregion:expressions + + // region:patterns + let (previous_pats, previous_unknown, previous_partially_unknown) = + (num_pats, num_pats_unknown, num_pats_partially_unknown); + for (pat_id, _) in body.pats.iter() { + let ty = &inference_result[pat_id]; + num_pats += 1; + let unknown_or_partial = if ty.is_unknown() { + num_pats_unknown += 1; + if verbosity.is_spammy() { + if let Some((path, start, end)) = + pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{} {}:{}-{}:{}: Unknown type", + path, + start.line + 1, + start.col, + end.line + 1, + end.col, + )); + } else { + bar.println(format!("{name}: Unknown type",)); + } + } + true + } else { + let is_partially_unknown = + ty.data(Interner).flags.contains(TypeFlags::HAS_ERROR); + if is_partially_unknown { + num_pats_partially_unknown += 1; + } + is_partially_unknown + }; + if self.only.is_some() && verbosity.is_spammy() { + // in super-verbose mode for just one function, we print every single pattern + if let Some((_, start, end)) = pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{}:{}-{}:{}: {}", + start.line + 1, + start.col, + end.line + 1, + end.col, + ty.display(db) + )); + } else { + bar.println(format!("unknown location: {}", ty.display(db))); + } + } + if unknown_or_partial && self.output == Some(OutputFormat::Csv) { + println!( + r#"{},type,"{}""#, + location_csv_pat(db, &analysis, vfs, &sm, pat_id), + ty.display(db) + ); + } + if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) { + num_pat_type_mismatches += 1; + if verbosity.is_verbose() { + if let Some((path, start, end)) = + pat_syntax_range(db, &analysis, vfs, &sm, pat_id) + { + bar.println(format!( + "{} {}:{}-{}:{}: Expected {}, got {}", + path, + start.line + 1, + start.col, + end.line + 1, + end.col, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } else { + bar.println(format!( + "{}: Expected {}, got {}", + name, + mismatch.expected.display(db), + mismatch.actual.display(db) + )); + } + } + if self.output == Some(OutputFormat::Csv) { + println!( + r#"{},mismatch,"{}","{}""#, + location_csv_pat(db, &analysis, vfs, &sm, pat_id), + mismatch.expected.display(db), + mismatch.actual.display(db) + ); + } + } + } + if verbosity.is_spammy() { + bar.println(format!( + "In {}: {} pats, {} unknown, {} partial", + full_name, + num_pats - previous_pats, + num_pats_unknown - previous_unknown, + num_pats_partially_unknown - previous_partially_unknown + )); + } + // endregion:patterns bar.inc(1); } @@ -366,10 +475,21 @@ impl flags::AnalysisStats { percentage(num_exprs_unknown, num_exprs), num_exprs_partially_unknown, percentage(num_exprs_partially_unknown, num_exprs), - num_type_mismatches + num_expr_type_mismatches + ); + eprintln!( + " pats: {}, ??ty: {} ({}%), ?ty: {} ({}%), !ty: {}", + num_pats, + num_pats_unknown, + percentage(num_pats_unknown, num_pats), + num_pats_partially_unknown, + percentage(num_pats_partially_unknown, num_pats), + num_pat_type_mismatches ); report_metric("unknown type", num_exprs_unknown, "#"); - report_metric("type mismatches", num_type_mismatches, "#"); + report_metric("type mismatches", num_expr_type_mismatches, "#"); + report_metric("pattern unknown type", num_pats_unknown, "#"); + report_metric("pattern type mismatches", num_pat_type_mismatches, "#"); eprintln!("{:<20} {}", "Inference:", inference_sw.elapsed()); } @@ -379,7 +499,7 @@ impl flags::AnalysisStats { } } -fn location_csv( +fn location_csv_expr( db: &RootDatabase, analysis: &Analysis, vfs: &Vfs, @@ -401,6 +521,30 @@ fn location_csv( format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col) } +fn location_csv_pat( + db: &RootDatabase, + analysis: &Analysis, + vfs: &Vfs, + sm: &BodySourceMap, + pat_id: PatId, +) -> String { + let src = match sm.pat_syntax(pat_id) { + Ok(s) => s, + Err(SyntheticSyntax) => return "synthetic,,".to_string(), + }; + let root = db.parse_or_expand(src.file_id).unwrap(); + let node = src.map(|e| { + e.either(|it| it.to_node(&root).syntax().clone(), |it| it.to_node(&root).syntax().clone()) + }); + let original_range = node.as_ref().original_file_range(db); + let path = vfs.file_path(original_range.file_id); + let line_index = analysis.file_line_index(original_range.file_id).unwrap(); + let text_range = original_range.range; + let (start, end) = + (line_index.line_col(text_range.start()), line_index.line_col(text_range.end())); + format!("{path},{}:{},{}:{}", start.line + 1, start.col, end.line + 1, end.col) +} + fn expr_syntax_range( db: &RootDatabase, analysis: &Analysis, @@ -423,6 +567,33 @@ fn expr_syntax_range( None } } +fn pat_syntax_range( + db: &RootDatabase, + analysis: &Analysis, + vfs: &Vfs, + sm: &BodySourceMap, + pat_id: PatId, +) -> Option<(VfsPath, LineCol, LineCol)> { + let src = sm.pat_syntax(pat_id); + if let Ok(src) = src { + let root = db.parse_or_expand(src.file_id).unwrap(); + let node = src.map(|e| { + e.either( + |it| it.to_node(&root).syntax().clone(), + |it| it.to_node(&root).syntax().clone(), + ) + }); + let original_range = node.as_ref().original_file_range(db); + let path = vfs.file_path(original_range.file_id); + let line_index = analysis.file_line_index(original_range.file_id).unwrap(); + let text_range = original_range.range; + let (start, end) = + (line_index.line_col(text_range.start()), line_index.line_col(text_range.end())); + Some((path, start, end)) + } else { + None + } +} fn shuffle<T>(rng: &mut Rand32, slice: &mut [T]) { for i in 0..slice.len() { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs index ff821be53..4006d023d 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/diagnostics.rs @@ -1,6 +1,7 @@ //! Analyze all modules in a project for diagnostics. Exits with a non-zero //! status code if any errors are found. +use project_model::{CargoConfig, RustLibSource}; use rustc_hash::FxHashSet; use hir::{db::HirDatabase, Crate, Module}; @@ -14,7 +15,8 @@ use crate::cli::{ impl flags::Diagnostics { pub fn run(self) -> anyhow::Result<()> { - let cargo_config = Default::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustLibSource::Discover); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: !self.disable_build_scripts, with_proc_macro_server: ProcMacroServerChoice::Sysroot, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs index 3fc1aa4ea..7f5d08449 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/lsif.rs @@ -13,7 +13,7 @@ use ide_db::LineIndexDatabase; use ide_db::base_db::salsa::{self, ParallelDatabase}; use ide_db::line_index::WideEncoding; use lsp_types::{self, lsif}; -use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource}; use vfs::{AbsPathBuf, Vfs}; use crate::cli::load_cargo::ProcMacroServerChoice; @@ -289,7 +289,8 @@ impl flags::Lsif { pub fn run(self) -> Result<()> { eprintln!("Generating LSIF started..."); let now = Instant::now(); - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustLibSource::Discover); let no_progress = &|_| (); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs index 9a04fbea7..3e5e40750 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/scip.rs @@ -15,7 +15,7 @@ use ide::{ TokenStaticData, }; use ide_db::LineIndexDatabase; -use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace}; +use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource}; use scip::types as scip_types; use std::env; @@ -29,7 +29,8 @@ impl flags::Scip { pub fn run(self) -> Result<()> { eprintln!("Generating SCIP start..."); let now = Instant::now(); - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustLibSource::Discover); let no_progress = &|s| (eprintln!("rust-analyzer: Loading {s}")); let load_cargo_config = LoadCargoConfig { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs index 3552f840a..82a769347 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/cli/ssr.rs @@ -1,7 +1,7 @@ //! Applies structured search replace rules from the command line. use ide_ssr::MatchFinder; -use project_model::CargoConfig; +use project_model::{CargoConfig, RustLibSource}; use crate::cli::{ flags, @@ -12,7 +12,8 @@ use crate::cli::{ impl flags::Ssr { pub fn run(self) -> Result<()> { use ide_db::base_db::SourceDatabaseExt; - let cargo_config = CargoConfig::default(); + let mut cargo_config = CargoConfig::default(); + cargo_config.sysroot = Some(RustLibSource::Discover); let load_cargo_config = LoadCargoConfig { load_out_dirs_from_check: true, with_proc_macro_server: ProcMacroServerChoice::Sysroot, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index f609a50a0..c35cce103 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -22,7 +22,7 @@ use ide_db::{ use itertools::Itertools; use lsp_types::{ClientCapabilities, MarkupKind}; use project_model::{ - CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, + CargoConfig, CargoFeatures, ProjectJson, ProjectJsonData, ProjectManifest, RustLibSource, UnsetTestCrates, }; use rustc_hash::{FxHashMap, FxHashSet}; @@ -101,6 +101,8 @@ config_data! { /// Use `RUSTC_WRAPPER=rust-analyzer` when running build scripts to /// avoid checking unnecessary things. cargo_buildScripts_useRustcWrapper: bool = "true", + /// Extra arguments that are passed to every cargo invocation. + cargo_extraArgs: Vec<String> = "[]", /// Extra environment variables that will be set when running cargo, rustc /// or other commands within the workspace. Useful for setting RUSTFLAGS. cargo_extraEnv: FxHashMap<String, String> = "{}", @@ -270,7 +272,6 @@ config_data! { /// The warnings will be indicated by a blue squiggly underline in code /// and a blue icon in the `Problems Panel`. diagnostics_warningsAsInfo: Vec<String> = "[]", - /// These directories will be ignored by rust-analyzer. They are /// relative to the workspace root, and globs are not supported. You may /// also need to add the folders to Code's `files.watcherExclude`. @@ -366,6 +367,8 @@ config_data! { inlayHints_typeHints_hideClosureInitialization: bool = "false", /// Whether to hide inlay type hints for constructors. inlayHints_typeHints_hideNamedConstructor: bool = "false", + /// Enables the experimental support for interpreting tests. + interpret_tests: bool = "false", /// Join lines merges consecutive declaration and initialization of an assignment. joinLines_joinAssignments: bool = "true", @@ -456,7 +459,10 @@ config_data! { /// Additional arguments to `rustfmt`. rustfmt_extraArgs: Vec<String> = "[]", /// Advanced option, fully override the command rust-analyzer uses for - /// formatting. + /// formatting. This should be the equivalent of `rustfmt` here, and + /// not that of `cargo fmt`. The file contents will be passed on the + /// standard input and the formatted result will be read from the + /// standard output. rustfmt_overrideCommand: Option<Vec<String>> = "null", /// Enables the use of rustfmt's unstable range formatting command for the /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only @@ -849,27 +855,27 @@ impl Config { } pub fn linked_projects(&self) -> Vec<LinkedProject> { match self.data.linkedProjects.as_slice() { - [] => match self.discovered_projects.as_ref() { - Some(discovered_projects) => { - let exclude_dirs: Vec<_> = self - .data - .files_excludeDirs - .iter() - .map(|p| self.root_path.join(p)) - .collect(); - discovered_projects + [] => { + match self.discovered_projects.as_ref() { + Some(discovered_projects) => { + let exclude_dirs: Vec<_> = self + .data + .files_excludeDirs + .iter() + .map(|p| self.root_path.join(p)) + .collect(); + discovered_projects .iter() - .filter(|p| { - let (ProjectManifest::ProjectJson(path) - | ProjectManifest::CargoToml(path)) = p; + .filter(|(ProjectManifest::ProjectJson(path) | ProjectManifest::CargoToml(path))| { !exclude_dirs.iter().any(|p| path.starts_with(p)) }) .cloned() .map(LinkedProject::from) .collect() + } + None => Vec::new(), } - None => Vec::new(), - }, + } linked_projects => linked_projects .iter() .filter_map(|linked_project| match linked_project { @@ -888,6 +894,15 @@ impl Config { } } + pub fn add_linked_projects(&mut self, linked_projects: Vec<ProjectJsonData>) { + let mut linked_projects = linked_projects + .into_iter() + .map(ManifestOrProjectJson::ProjectJson) + .collect::<Vec<ManifestOrProjectJson>>(); + + self.data.linkedProjects.append(&mut linked_projects); + } + pub fn did_save_text_document_dynamic_registration(&self) -> bool { let caps = try_or_def!(self.caps.text_document.as_ref()?.synchronization.clone()?); caps.did_save == Some(true) && caps.dynamic_registration == Some(true) @@ -1050,10 +1065,20 @@ impl Config { } } + pub fn extra_args(&self) -> &Vec<String> { + &self.data.cargo_extraArgs + } + pub fn extra_env(&self) -> &FxHashMap<String, String> { &self.data.cargo_extraEnv } + pub fn check_extra_args(&self) -> Vec<String> { + let mut extra_args = self.extra_args().clone(); + extra_args.extend_from_slice(&self.data.check_extraArgs); + extra_args + } + pub fn check_extra_env(&self) -> FxHashMap<String, String> { let mut extra_env = self.data.cargo_extraEnv.clone(); extra_env.extend(self.data.check_extraEnv.clone()); @@ -1112,16 +1137,16 @@ impl Config { pub fn cargo(&self) -> CargoConfig { let rustc_source = self.data.rustc_source.as_ref().map(|rustc_src| { if rustc_src == "discover" { - RustcSource::Discover + RustLibSource::Discover } else { - RustcSource::Path(self.root_path.join(rustc_src)) + RustLibSource::Path(self.root_path.join(rustc_src)) } }); let sysroot = self.data.cargo_sysroot.as_ref().map(|sysroot| { if sysroot == "discover" { - RustcSource::Discover + RustLibSource::Discover } else { - RustcSource::Path(self.root_path.join(sysroot)) + RustLibSource::Path(self.root_path.join(sysroot)) } }); let sysroot_src = @@ -1152,6 +1177,7 @@ impl Config { InvocationLocation::Workspace => project_model::InvocationLocation::Workspace, }, run_build_script_command: self.data.cargo_buildScripts_overrideCommand.clone(), + extra_args: self.data.cargo_extraArgs.clone(), extra_env: self.data.cargo_extraEnv.clone(), } } @@ -1222,7 +1248,7 @@ impl Config { CargoFeaturesDef::All => vec![], CargoFeaturesDef::Selected(it) => it, }, - extra_args: self.data.check_extraArgs.clone(), + extra_args: self.check_extra_args(), extra_env: self.check_extra_env(), ansi_color_output: self.color_diagnostic_output(), }, @@ -1441,6 +1467,7 @@ impl Config { } }, keywords: self.data.hover_documentation_keywords_enable, + interpret_tests: self.data.interpret_tests, } } diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs index 715804449..313bb2ec8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/dispatch.rs @@ -88,6 +88,42 @@ impl<'a> RequestDispatcher<'a> { } /// Dispatches the request onto thread pool + pub(crate) fn on_no_retry<R>( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>, + ) -> &mut Self + where + R: lsp_types::request::Request + 'static, + R::Params: DeserializeOwned + panic::UnwindSafe + Send + fmt::Debug, + R::Result: Serialize, + { + let (req, params, panic_context) = match self.parse::<R>() { + Some(it) => it, + None => return self, + }; + + self.global_state.task_pool.handle.spawn({ + let world = self.global_state.snapshot(); + move || { + let result = panic::catch_unwind(move || { + let _pctx = stdx::panic_context::enter(panic_context); + f(world, params) + }); + match thread_result_to_response::<R>(req.id.clone(), result) { + Ok(response) => Task::Response(response), + Err(_) => Task::Response(lsp_server::Response::new_err( + req.id, + lsp_server::ErrorCode::ContentModified as i32, + "content modified".to_string(), + )), + } + } + }); + + self + } + + /// Dispatches the request onto thread pool pub(crate) fn on<R>( &mut self, f: fn(GlobalStateSnapshot, R::Params) -> Result<R::Result>, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs index 4e08bd0a7..2fca2ab85 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs @@ -29,7 +29,7 @@ use project_model::{ManifestPath, ProjectWorkspace, TargetKind}; use serde_json::json; use stdx::{format_to, never}; use syntax::{algo, ast, AstNode, TextRange, TextSize}; -use vfs::AbsPathBuf; +use vfs::{AbsPath, AbsPathBuf}; use crate::{ cargo_target_spec::CargoTargetSpec, @@ -46,6 +46,7 @@ use crate::{ pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> { state.proc_macro_clients.clear(); state.proc_macro_changed = false; + state.fetch_workspaces_queue.request_op("reload workspace request".to_string()); state.fetch_build_data_queue.request_op("reload workspace request".to_string()); Ok(()) @@ -84,6 +85,15 @@ pub(crate) fn handle_analyzer_status( snap.workspaces.len(), if snap.workspaces.len() == 1 { "" } else { "s" } ); + + format_to!( + buf, + "Workspace root folders: {:?}", + snap.workspaces + .iter() + .flat_map(|ws| ws.workspace_definition_path()) + .collect::<Vec<&AbsPath>>() + ); } buf.push_str("\nAnalysis:\n"); buf.push_str( @@ -134,6 +144,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/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs index e33589cc5..c7b513db9 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_ext.rs +++ b/src/tools/rust-analyzer/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/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs index 30f1c53c1..12e5caf2c 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp_utils.rs @@ -36,11 +36,41 @@ impl Progress { } impl GlobalState { - pub(crate) fn show_message(&mut self, typ: lsp_types::MessageType, message: String) { - let message = message; - self.send_notification::<lsp_types::notification::ShowMessage>( - lsp_types::ShowMessageParams { typ, message }, - ) + pub(crate) fn show_message( + &mut self, + typ: lsp_types::MessageType, + message: String, + show_open_log_button: bool, + ) { + match self.config.open_server_logs() && show_open_log_button { + true => self.send_request::<lsp_types::request::ShowMessageRequest>( + lsp_types::ShowMessageRequestParams { + typ, + message, + actions: Some(vec![lsp_types::MessageActionItem { + title: "Open server logs".to_owned(), + properties: Default::default(), + }]), + }, + |this, resp| { + let lsp_server::Response { error: None, result: Some(result), .. } = resp + else { return }; + if let Ok(Some(_item)) = crate::from_json::< + <lsp_types::request::ShowMessageRequest as lsp_types::request::Request>::Result, + >( + lsp_types::request::ShowMessageRequest::METHOD, &result + ) { + this.send_notification::<lsp_ext::OpenServerLogs>(()); + } + }, + ), + false => self.send_notification::<lsp_types::notification::ShowMessage>( + lsp_types::ShowMessageParams { + typ, + message, + }, + ), + } } /// Sends a notification to the client containing the error `message`. diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index d1e38b33c..67a54cde6 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -111,12 +111,7 @@ impl fmt::Debug for Event { impl GlobalState { fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { - if self.config.linked_projects().is_empty() - && self.config.detached_files().is_empty() - && self.config.notifications().cargo_toml_not_found - { - self.show_and_log_error("rust-analyzer failed to discover workspace".to_string(), None); - }; + self.update_status_or_notify(); if self.config.did_save_text_document_dynamic_registration() { let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions { @@ -323,17 +318,6 @@ impl GlobalState { if let Some(diagnostic_changes) = self.diagnostics.take_changes() { for file_id in diagnostic_changes { - let db = self.analysis_host.raw_database(); - let source_root = db.file_source_root(file_id); - if db.source_root(source_root).is_library { - // Only publish diagnostics for files in the workspace, not from crates.io deps - // or the sysroot. - // While theoretically these should never have errors, we have quite a few false - // positives particularly in the stdlib, and those diagnostics would stay around - // forever if we emitted them here. - continue; - } - let uri = file_id_to_url(&self.vfs.read().0, file_id); let mut diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>(); @@ -405,25 +389,38 @@ impl GlobalState { }); } + self.update_status_or_notify(); + + let loop_duration = loop_start.elapsed(); + if loop_duration > Duration::from_millis(100) && was_quiescent { + tracing::warn!("overly long loop turn: {:?}", loop_duration); + self.poke_rust_analyzer_developer(format!("overly long loop turn: {loop_duration:?}")); + } + Ok(()) + } + + fn update_status_or_notify(&mut self) { let status = self.current_status(); if self.last_reported_status.as_ref() != Some(&status) { self.last_reported_status = Some(status.clone()); - if let (lsp_ext::Health::Error, Some(message)) = (status.health, &status.message) { - self.show_message(lsp_types::MessageType::ERROR, message.clone()); - } - if self.config.server_status_notification() { self.send_notification::<lsp_ext::ServerStatusNotification>(status); + } else if let (health, Some(message)) = (status.health, &status.message) { + let open_log_button = tracing::enabled!(tracing::Level::ERROR) + && (self.fetch_build_data_error().is_err() + || self.fetch_workspace_error().is_err()); + self.show_message( + match health { + lsp_ext::Health::Ok => lsp_types::MessageType::INFO, + lsp_ext::Health::Warning => lsp_types::MessageType::WARNING, + lsp_ext::Health::Error => lsp_types::MessageType::ERROR, + }, + message.clone(), + open_log_button, + ); } } - - let loop_duration = loop_start.elapsed(); - if loop_duration > Duration::from_millis(100) && was_quiescent { - tracing::warn!("overly long loop turn: {:?}", loop_duration); - self.poke_rust_analyzer_developer(format!("overly long loop turn: {loop_duration:?}")); - } - Ok(()) } fn handle_task(&mut self, prime_caches_progress: &mut Vec<PrimeCachesProgress>, task: Task) { @@ -456,6 +453,9 @@ impl GlobalState { ProjectWorkspaceProgress::Report(msg) => (Progress::Report, Some(msg)), ProjectWorkspaceProgress::End(workspaces) => { self.fetch_workspaces_queue.op_completed(Some(workspaces)); + if let Err(e) = self.fetch_workspace_error() { + tracing::error!("FetchWorkspaceError:\n{e}"); + } let old = Arc::clone(&self.workspaces); self.switch_workspaces("fetched workspace".to_string()); @@ -477,6 +477,9 @@ impl GlobalState { BuildDataProgress::Report(msg) => (Some(Progress::Report), Some(msg)), BuildDataProgress::End(build_data_result) => { self.fetch_build_data_queue.op_completed(build_data_result); + if let Err(e) = self.fetch_build_data_error() { + tracing::error!("FetchBuildDataError:\n{e}"); + } self.switch_workspaces("fetched build data".to_string()); @@ -509,6 +512,7 @@ impl GlobalState { self.vfs_progress_n_total = n_total; self.vfs_progress_n_done = n_done; + // if n_total != 0 { let state = if n_done == 0 { Progress::Begin } else if n_done < n_total { @@ -523,7 +527,8 @@ impl GlobalState { Some(format!("{n_done}/{n_total}")), Some(Progress::fraction(n_done, n_total)), None, - ) + ); + // } } } } @@ -565,7 +570,10 @@ impl GlobalState { flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)), flycheck::Progress::DidCancel => (Progress::End, None), flycheck::Progress::DidFailToRestart(err) => { - self.show_and_log_error("cargo check failed".to_string(), Some(err)); + self.show_and_log_error( + "cargo check failed to start".to_string(), + Some(err), + ); return; } flycheck::Progress::DidFinish(result) => { @@ -634,6 +642,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) @@ -654,7 +663,7 @@ impl GlobalState { .on::<lsp_types::request::GotoDeclaration>(handlers::handle_goto_declaration) .on::<lsp_types::request::GotoImplementation>(handlers::handle_goto_implementation) .on::<lsp_types::request::GotoTypeDefinition>(handlers::handle_goto_type_definition) - .on::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints) + .on_no_retry::<lsp_types::request::InlayHintRequest>(handlers::handle_inlay_hints) .on::<lsp_types::request::InlayHintResolveRequest>(handlers::handle_inlay_hints_resolve) .on::<lsp_types::request::Completion>(handlers::handle_completion) .on::<lsp_types::request::ResolveCompletionItem>(handlers::handle_completion_resolve) @@ -920,6 +929,7 @@ impl GlobalState { this.show_message( lsp_types::MessageType::WARNING, error.to_string(), + false, ); } this.update_configuration(config); @@ -971,10 +981,20 @@ impl GlobalState { } fn update_diagnostics(&mut self) { + let db = self.analysis_host.raw_database(); let subscriptions = self .mem_docs .iter() .map(|path| self.vfs.read().0.file_id(path).unwrap()) + .filter(|&file_id| { + let source_root = db.file_source_root(file_id); + // Only publish diagnostics for files in the workspace, not from crates.io deps + // or the sysroot. + // While theoretically these should never have errors, we have quite a few false + // positives particularly in the stdlib, and those diagnostics would stay around + // forever if we emitted them here. + !db.source_root(source_root).is_library + }) .collect::<Vec<_>>(); tracing::trace!("updating notifications for {:?}", subscriptions); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index abce0d737..1a6e1af2e 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -12,17 +12,21 @@ //! correct. Instead, we try to provide a best-effort service. Even if the //! project is currently loading and we don't have a full project model, we //! still want to respond to various requests. -use std::{mem, sync::Arc}; +use std::{collections::hash_map::Entry, mem, sync::Arc}; use flycheck::{FlycheckConfig, FlycheckHandle}; use hir::db::DefDatabase; use ide::Change; -use ide_db::base_db::{ - CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, - ProcMacroLoadResult, SourceRoot, VfsPath, +use ide_db::{ + base_db::{ + CrateGraph, Env, ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, + ProcMacroLoadResult, SourceRoot, VfsPath, + }, + FxHashMap, }; +use itertools::Itertools; use proc_macro_api::{MacroDylib, ProcMacroServer}; -use project_model::{ProjectWorkspace, WorkspaceBuildScripts}; +use project_model::{PackageRoot, ProjectWorkspace, WorkspaceBuildScripts}; use syntax::SmolStr; use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind}; @@ -52,7 +56,8 @@ pub(crate) enum BuildDataProgress { impl GlobalState { pub(crate) fn is_quiescent(&self) -> bool { - !(self.fetch_workspaces_queue.op_in_progress() + !(self.last_reported_status.is_none() + || self.fetch_workspaces_queue.op_in_progress() || self.fetch_build_data_queue.op_in_progress() || self.vfs_progress_config_version < self.vfs_config_version || self.vfs_progress_n_done < self.vfs_progress_n_total) @@ -85,36 +90,54 @@ impl GlobalState { quiescent: self.is_quiescent(), message: None, }; + let mut message = String::new(); if self.proc_macro_changed { status.health = lsp_ext::Health::Warning; - status.message = - Some("Reload required due to source changes of a procedural macro.".into()) + message.push_str("Reload required due to source changes of a procedural macro.\n\n"); } if let Err(_) = self.fetch_build_data_error() { status.health = lsp_ext::Health::Warning; - status.message = - Some("Failed to run build scripts of some packages, check the logs.".to_string()); + message.push_str("Failed to run build scripts of some packages.\n\n"); } if !self.config.cargo_autoreload() && self.is_quiescent() && self.fetch_workspaces_queue.op_requested() { status.health = lsp_ext::Health::Warning; - status.message = Some("Workspace reload required".to_string()) + message.push_str("Auto-reloading is disabled and the workspace has changed, a manual workspace reload is required.\n\n"); } - - if let Err(error) = self.fetch_workspace_error() { - status.health = lsp_ext::Health::Error; - status.message = Some(error) - } - if self.config.linked_projects().is_empty() && self.config.detached_files().is_empty() && self.config.notifications().cargo_toml_not_found { status.health = lsp_ext::Health::Warning; - status.message = Some("Workspace reload required".to_string()) + message.push_str("Failed to discover workspace.\n\n"); + } + + for ws in self.workspaces.iter() { + let (ProjectWorkspace::Cargo { sysroot, .. } + | ProjectWorkspace::Json { sysroot, .. } + | ProjectWorkspace::DetachedFiles { sysroot, .. }) = ws; + if let Err(Some(e)) = sysroot { + status.health = lsp_ext::Health::Warning; + message.push_str(e); + message.push_str("\n\n"); + } + if let ProjectWorkspace::Cargo { rustc: Err(Some(e)), .. } = ws { + status.health = lsp_ext::Health::Warning; + message.push_str(e); + message.push_str("\n\n"); + } + } + + if let Err(_) = self.fetch_workspace_error() { + status.health = lsp_ext::Health::Error; + message.push_str("Failed to load workspaces.\n\n"); + } + + if !message.is_empty() { + status.message = Some(message.trim_end().to_owned()); } status } @@ -197,8 +220,7 @@ impl GlobalState { let _p = profile::span("GlobalState::switch_workspaces"); tracing::info!(%cause, "will switch workspaces"); - if let Err(error_message) = self.fetch_workspace_error() { - self.show_and_log_error(error_message, None); + if let Err(_) = self.fetch_workspace_error() { if !self.workspaces.is_empty() { // It only makes sense to switch to a partially broken workspace // if we don't have any workspace at all yet. @@ -206,10 +228,6 @@ impl GlobalState { } } - if let Err(error) = self.fetch_build_data_error() { - self.show_and_log_error("failed to run build scripts".to_string(), Some(error)); - } - let Some(workspaces) = self.fetch_workspaces_queue.last_op_result() else { return; }; let workspaces = workspaces.iter().filter_map(|res| res.as_ref().ok().cloned()).collect::<Vec<_>>(); @@ -388,7 +406,7 @@ impl GlobalState { tracing::info!("did switch workspaces"); } - fn fetch_workspace_error(&self) -> Result<(), String> { + pub(super) fn fetch_workspace_error(&self) -> Result<(), String> { let mut buf = String::new(); let Some(last_op_result) = self.fetch_workspaces_queue.last_op_result() else { return Ok(()) }; @@ -409,7 +427,7 @@ impl GlobalState { Err(buf) } - fn fetch_build_data_error(&self) -> Result<(), String> { + pub(super) fn fetch_build_data_error(&self) -> Result<(), String> { let mut buf = String::new(); for ws in &self.fetch_build_data_queue.last_op_result().1 { @@ -494,7 +512,69 @@ impl ProjectFolders { let mut fsc = FileSetConfig::builder(); let mut local_filesets = vec![]; - for root in workspaces.iter().flat_map(|ws| ws.to_roots()) { + // Dedup source roots + // Depending on the project setup, we can have duplicated source roots, or for example in + // the case of the rustc workspace, we can end up with two source roots that are almost the + // same but not quite, like: + // PackageRoot { is_local: false, include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri")], exclude: [] } + // PackageRoot { + // is_local: true, + // include: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri"), AbsPathBuf(".../rust/build/x86_64-pc-windows-msvc/stage0-tools/x86_64-pc-windows-msvc/release/build/cargo-miri-85801cd3d2d1dae4/out")], + // exclude: [AbsPathBuf(".../rust/src/tools/miri/cargo-miri/.git"), AbsPathBuf(".../rust/src/tools/miri/cargo-miri/target")] + // } + // + // The first one comes from the explicit rustc workspace which points to the rustc workspace itself + // The second comes from the rustc workspace that we load as the actual project workspace + // These `is_local` differing in this kind of way gives us problems, especially when trying to filter diagnostics as we don't report diagnostics for external libraries. + // So we need to deduplicate these, usually it would be enough to deduplicate by `include`, but as the rustc example shows here that doesn't work, + // so we need to also coalesce the includes if they overlap. + + let mut roots: Vec<_> = workspaces + .iter() + .flat_map(|ws| ws.to_roots()) + .update(|root| root.include.sort()) + .sorted_by(|a, b| a.include.cmp(&b.include)) + .collect(); + + // map that tracks indices of overlapping roots + let mut overlap_map = FxHashMap::<_, Vec<_>>::default(); + let mut done = false; + + while !mem::replace(&mut done, true) { + // maps include paths to indices of the corresponding root + let mut include_to_idx = FxHashMap::default(); + // Find and note down the indices of overlapping roots + for (idx, root) in roots.iter().enumerate().filter(|(_, it)| !it.include.is_empty()) { + for include in &root.include { + match include_to_idx.entry(include) { + Entry::Occupied(e) => { + overlap_map.entry(*e.get()).or_default().push(idx); + } + Entry::Vacant(e) => { + e.insert(idx); + } + } + } + } + for (k, v) in overlap_map.drain() { + done = false; + for v in v { + let r = mem::replace( + &mut roots[v], + PackageRoot { is_local: false, include: vec![], exclude: vec![] }, + ); + roots[k].is_local |= r.is_local; + roots[k].include.extend(r.include); + roots[k].exclude.extend(r.exclude); + } + roots[k].include.sort(); + roots[k].exclude.sort(); + roots[k].include.dedup(); + roots[k].exclude.dedup(); + } + } + + for root in roots.into_iter().filter(|it| !it.include.is_empty()) { let file_set_roots: Vec<VfsPath> = root.include.iter().cloned().map(VfsPath::from).collect(); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs index 92029dc1d..7d97b69f8 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/to_proto.rs @@ -50,7 +50,7 @@ pub(crate) fn symbol_kind(symbol_kind: SymbolKind) -> lsp_types::SymbolKind { SymbolKind::Struct => lsp_types::SymbolKind::STRUCT, SymbolKind::Enum => lsp_types::SymbolKind::ENUM, SymbolKind::Variant => lsp_types::SymbolKind::ENUM_MEMBER, - SymbolKind::Trait => lsp_types::SymbolKind::INTERFACE, + SymbolKind::Trait | SymbolKind::TraitAlias => lsp_types::SymbolKind::INTERFACE, SymbolKind::Macro | SymbolKind::BuiltinAttr | SymbolKind::Attribute @@ -135,6 +135,7 @@ pub(crate) fn completion_item_kind( SymbolKind::Static => lsp_types::CompletionItemKind::VALUE, SymbolKind::Struct => lsp_types::CompletionItemKind::STRUCT, SymbolKind::Trait => lsp_types::CompletionItemKind::INTERFACE, + SymbolKind::TraitAlias => lsp_types::CompletionItemKind::INTERFACE, SymbolKind::TypeAlias => lsp_types::CompletionItemKind::STRUCT, SymbolKind::TypeParam => lsp_types::CompletionItemKind::TYPE_PARAMETER, SymbolKind::Union => lsp_types::CompletionItemKind::STRUCT, @@ -656,6 +657,7 @@ fn semantic_token_type_and_modifiers( SymbolKind::Union => semantic_tokens::UNION, SymbolKind::TypeAlias => semantic_tokens::TYPE_ALIAS, SymbolKind::Trait => semantic_tokens::INTERFACE, + SymbolKind::TraitAlias => semantic_tokens::INTERFACE, SymbolKind::Macro => semantic_tokens::MACRO, SymbolKind::BuiltinAttr => semantic_tokens::BUILTIN_ATTRIBUTE, SymbolKind::ToolModule => semantic_tokens::TOOL_MODULE, diff --git a/src/tools/rust-analyzer/crates/syntax/Cargo.toml b/src/tools/rust-analyzer/crates/syntax/Cargo.toml index 8fc493a23..305cf2d39 100644 --- a/src/tools/rust-analyzer/crates/syntax/Cargo.toml +++ b/src/tools/rust-analyzer/crates/syntax/Cargo.toml @@ -14,6 +14,7 @@ doctest = false [dependencies] cov-mark = "2.0.0-pre.1" +either = "1.7.0" itertools = "0.10.5" rowan = "0.15.10" rustc_lexer = { version = "727.0.0", package = "rustc-ap-rustc_lexer" } diff --git a/src/tools/rust-analyzer/crates/syntax/rust.ungram b/src/tools/rust-analyzer/crates/syntax/rust.ungram index 36ad5fddf..548b5ba8b 100644 --- a/src/tools/rust-analyzer/crates/syntax/rust.ungram +++ b/src/tools/rust-analyzer/crates/syntax/rust.ungram @@ -97,6 +97,7 @@ Item = | Static | Struct | Trait +| TraitAlias | TypeAlias | Union | Use @@ -240,10 +241,11 @@ Trait = Attr* Visibility? 'unsafe'? 'auto'? 'trait' Name GenericParamList? - ( - (':' TypeBoundList?)? WhereClause? AssocItemList - | '=' TypeBoundList? WhereClause? ';' - ) + (':' TypeBoundList?)? WhereClause? AssocItemList + +TraitAlias = + Attr* Visibility? + 'trait' Name GenericParamList? '=' TypeBoundList? WhereClause? ';' AssocItemList = '{' Attr* AssocItem* '}' diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast.rs b/src/tools/rust-analyzer/crates/syntax/src/ast.rs index 385a4e0a3..1e691beff 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast.rs @@ -13,7 +13,7 @@ pub mod prec; use std::marker::PhantomData; -use itertools::Either; +use either::Either; use crate::{ syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken}, @@ -25,7 +25,8 @@ pub use self::{ generated::{nodes::*, tokens::*}, node_ext::{ AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind, - SlicePatComponents, StructKind, TypeBoundKind, TypeOrConstParam, VisibilityKind, + SlicePatComponents, StructKind, TraitOrAlias, TypeBoundKind, TypeOrConstParam, + VisibilityKind, }, operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp}, token_ext::{CommentKind, CommentPlacement, CommentShape, IsString, QuoteOffsets, Radix}, @@ -128,6 +129,13 @@ where } } +impl<L, R> HasAttrs for Either<L, R> +where + L: HasAttrs, + R: HasAttrs, +{ +} + mod support { use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken}; diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs index db66d08a7..c43d0830b 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/expr_ext.rs @@ -48,23 +48,30 @@ impl From<ast::IfExpr> for ElseBranch { } impl ast::IfExpr { - pub fn then_branch(&self) -> Option<ast::BlockExpr> { - self.children_after_condition().next() + pub fn condition(&self) -> Option<ast::Expr> { + // If the condition is a BlockExpr, check if the then body is missing. + // If it is assume the condition is the expression that is missing instead. + let mut exprs = support::children(self.syntax()); + let first = exprs.next(); + match first { + Some(ast::Expr::BlockExpr(_)) => exprs.next().and(first), + first => first, + } } - pub fn else_branch(&self) -> Option<ElseBranch> { - let res = match self.children_after_condition().nth(1) { - Some(block) => ElseBranch::Block(block), - None => { - let elif = self.children_after_condition().next()?; - ElseBranch::IfExpr(elif) - } - }; - Some(res) + pub fn then_branch(&self) -> Option<ast::BlockExpr> { + match support::children(self.syntax()).nth(1)? { + ast::Expr::BlockExpr(block) => Some(block), + _ => None, + } } - fn children_after_condition<N: AstNode>(&self) -> impl Iterator<Item = N> { - self.syntax().children().skip(1).filter_map(N::cast) + pub fn else_branch(&self) -> Option<ElseBranch> { + match support::children(self.syntax()).nth(2)? { + ast::Expr::BlockExpr(block) => Some(ElseBranch::Block(block)), + ast::Expr::IfExpr(elif) => Some(ElseBranch::IfExpr(elif)), + _ => None, + } } } @@ -356,7 +363,15 @@ impl ast::BlockExpr { Some(it) => it, None => return true, }; - !matches!(parent.kind(), FN | IF_EXPR | WHILE_EXPR | LOOP_EXPR) + match parent.kind() { + FOR_EXPR | IF_EXPR => parent + .children() + .filter(|it| ast::Expr::can_cast(it.kind())) + .next() + .map_or(true, |it| it == *self.syntax()), + LET_ELSE | FN | WHILE_EXPR | LOOP_EXPR | CONST_BLOCK_PAT => false, + _ => true, + } } } diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs index 642a3bfc3..fe3248453 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/generated/nodes.rs @@ -407,7 +407,21 @@ impl Trait { pub fn auto_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![auto]) } pub fn trait_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![trait]) } pub fn assoc_item_list(&self) -> Option<AssocItemList> { support::child(&self.syntax) } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TraitAlias { + pub(crate) syntax: SyntaxNode, +} +impl ast::HasAttrs for TraitAlias {} +impl ast::HasName for TraitAlias {} +impl ast::HasVisibility for TraitAlias {} +impl ast::HasGenericParams for TraitAlias {} +impl ast::HasDocComments for TraitAlias {} +impl TraitAlias { + pub fn trait_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![trait]) } pub fn eq_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![=]) } + pub fn type_bound_list(&self) -> Option<TypeBoundList> { support::child(&self.syntax) } pub fn semicolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![;]) } } @@ -1573,6 +1587,7 @@ pub enum Item { Static(Static), Struct(Struct), Trait(Trait), + TraitAlias(TraitAlias), TypeAlias(TypeAlias), Union(Union), Use(Use), @@ -2058,6 +2073,17 @@ impl AstNode for Trait { } fn syntax(&self) -> &SyntaxNode { &self.syntax } } +impl AstNode for TraitAlias { + fn can_cast(kind: SyntaxKind) -> bool { kind == TRAIT_ALIAS } + fn cast(syntax: SyntaxNode) -> Option<Self> { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { &self.syntax } +} impl AstNode for TypeAlias { fn can_cast(kind: SyntaxKind) -> bool { kind == TYPE_ALIAS } fn cast(syntax: SyntaxNode) -> Option<Self> { @@ -3570,6 +3596,9 @@ impl From<Struct> for Item { impl From<Trait> for Item { fn from(node: Trait) -> Item { Item::Trait(node) } } +impl From<TraitAlias> for Item { + fn from(node: TraitAlias) -> Item { Item::TraitAlias(node) } +} impl From<TypeAlias> for Item { fn from(node: TypeAlias) -> Item { Item::TypeAlias(node) } } @@ -3596,6 +3625,7 @@ impl AstNode for Item { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -3616,6 +3646,7 @@ impl AstNode for Item { STATIC => Item::Static(Static { syntax }), STRUCT => Item::Struct(Struct { syntax }), TRAIT => Item::Trait(Trait { syntax }), + TRAIT_ALIAS => Item::TraitAlias(TraitAlias { syntax }), TYPE_ALIAS => Item::TypeAlias(TypeAlias { syntax }), UNION => Item::Union(Union { syntax }), USE => Item::Use(Use { syntax }), @@ -3638,6 +3669,7 @@ impl AstNode for Item { Item::Static(it) => &it.syntax, Item::Struct(it) => &it.syntax, Item::Trait(it) => &it.syntax, + Item::TraitAlias(it) => &it.syntax, Item::TypeAlias(it) => &it.syntax, Item::Union(it) => &it.syntax, Item::Use(it) => &it.syntax, @@ -3950,6 +3982,7 @@ impl AstNode for AnyHasAttrs { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4035,6 +4068,7 @@ impl AstNode for AnyHasDocComments { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4056,7 +4090,7 @@ impl AnyHasGenericParams { } impl AstNode for AnyHasGenericParams { fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION) + matches!(kind, ENUM | FN | IMPL | STRUCT | TRAIT | TRAIT_ALIAS | TYPE_ALIAS | UNION) } fn cast(syntax: SyntaxNode) -> Option<Self> { Self::can_cast(syntax.kind()).then_some(AnyHasGenericParams { syntax }) @@ -4108,6 +4142,7 @@ impl AstNode for AnyHasName { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | RENAME @@ -4163,6 +4198,7 @@ impl AstNode for AnyHasVisibility { | STATIC | STRUCT | TRAIT + | TRAIT_ALIAS | TYPE_ALIAS | UNION | USE @@ -4391,6 +4427,11 @@ impl std::fmt::Display for Trait { std::fmt::Display::fmt(self.syntax(), f) } } +impl std::fmt::Display for TraitAlias { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.syntax(), f) + } +} impl std::fmt::Display for TypeAlias { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs index fe82aa907..3308077da 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/node_ext.rs @@ -680,6 +680,81 @@ impl TypeOrConstParam { } } +impl AstNode for TypeOrConstParam { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!(kind, SyntaxKind::TYPE_PARAM | SyntaxKind::CONST_PARAM) + } + + fn cast(syntax: SyntaxNode) -> Option<Self> + where + Self: Sized, + { + let res = match syntax.kind() { + SyntaxKind::TYPE_PARAM => TypeOrConstParam::Type(ast::TypeParam { syntax }), + SyntaxKind::CONST_PARAM => TypeOrConstParam::Const(ast::ConstParam { syntax }), + _ => return None, + }; + Some(res) + } + + fn syntax(&self) -> &SyntaxNode { + match self { + TypeOrConstParam::Type(it) => it.syntax(), + TypeOrConstParam::Const(it) => it.syntax(), + } + } +} + +impl HasAttrs for TypeOrConstParam {} + +#[derive(Debug, Clone)] +pub enum TraitOrAlias { + Trait(ast::Trait), + TraitAlias(ast::TraitAlias), +} + +impl TraitOrAlias { + pub fn name(&self) -> Option<ast::Name> { + match self { + TraitOrAlias::Trait(x) => x.name(), + TraitOrAlias::TraitAlias(x) => x.name(), + } + } +} + +impl AstNode for TraitOrAlias { + fn can_cast(kind: SyntaxKind) -> bool + where + Self: Sized, + { + matches!(kind, SyntaxKind::TRAIT | SyntaxKind::TRAIT_ALIAS) + } + + fn cast(syntax: SyntaxNode) -> Option<Self> + where + Self: Sized, + { + let res = match syntax.kind() { + SyntaxKind::TRAIT => TraitOrAlias::Trait(ast::Trait { syntax }), + SyntaxKind::TRAIT_ALIAS => TraitOrAlias::TraitAlias(ast::TraitAlias { syntax }), + _ => return None, + }; + Some(res) + } + + fn syntax(&self) -> &SyntaxNode { + match self { + TraitOrAlias::Trait(it) => it.syntax(), + TraitOrAlias::TraitAlias(it) => it.syntax(), + } + } +} + +impl HasAttrs for TraitOrAlias {} + pub enum VisibilityKind { In(ast::Path), PubCrate, @@ -862,12 +937,6 @@ impl From<ast::Adt> for ast::Item { } } -impl ast::IfExpr { - pub fn condition(&self) -> Option<ast::Expr> { - support::child(&self.syntax) - } -} - impl ast::MatchGuard { pub fn condition(&self) -> Option<ast::Expr> { support::child(&self.syntax) diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs index aa2b7ed5c..3e43df2d0 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/traits.rs @@ -1,7 +1,7 @@ //! Various traits that are implemented by ast nodes. //! //! The implementations are usually trivial, and live in generated.rs -use itertools::Either; +use either::Either; use crate::{ ast::{self, support, AstChildren, AstNode, AstToken}, @@ -134,3 +134,5 @@ impl Iterator for AttrDocCommentIter { }) } } + +impl<A: HasName, B: HasName> HasName for Either<A, B> {} diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs index 3ff6e0300..ccce71966 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/tests/ast_src.rs @@ -86,6 +86,7 @@ pub(crate) const KINDS_SRC: KindsSrc<'_> = KindsSrc { "STATIC", "CONST", "TRAIT", + "TRAIT_ALIAS", "IMPL", "TYPE_ALIAS", "MACRO_CALL", diff --git a/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs b/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs index 03aa2c451..e954b5825 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/tests/sourcegen_ast.rs @@ -783,6 +783,7 @@ fn extract_struct_traits(ast: &mut AstSrc) { "Enum", "Variant", "Trait", + "TraitAlias", "Module", "Static", "Const", diff --git a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs index d1afd0039..cd1235fa6 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/fixture.rs @@ -180,7 +180,9 @@ impl Fixture { let mut cfg_key_values = Vec::new(); let mut env = FxHashMap::default(); let mut introduce_new_source_root = None; - let mut target_data_layout = None; + let mut target_data_layout = Some( + "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(), + ); for component in components[1..].iter() { let (key, value) = component.split_once(':').unwrap_or_else(|| panic!("invalid meta line: {meta:?}")); diff --git a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs index 3b033e1aa..ca6de4061 100644 --- a/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs +++ b/src/tools/rust-analyzer/crates/test-utils/src/minicore.rs @@ -44,6 +44,8 @@ //! try: infallible //! unsize: sized +#![rustc_coherence_is_core] + pub mod marker { // region:sized #[lang = "sized"] @@ -510,6 +512,7 @@ pub mod fmt { pub mod slice { #[lang = "slice"] impl<T> [T] { + #[lang = "slice_len_fn"] pub fn len(&self) -> usize { loop {} } @@ -533,6 +536,40 @@ pub mod option { None => panic!("called `Option::unwrap()` on a `None` value"), } } + + pub fn and<U>(self, optb: Option<U>) -> Option<U> { + loop {} + } + pub fn unwrap_or(self, default: T) -> T { + loop {} + } + // region:fn + pub fn and_then<U, F>(self, f: F) -> Option<U> + where + F: FnOnce(T) -> Option<U>, + { + loop {} + } + pub fn unwrap_or_else<F>(self, f: F) -> T + where + F: FnOnce() -> T, + { + loop {} + } + pub fn map_or<U, F>(self, default: U, f: F) -> U + where + F: FnOnce(T) -> U, + { + loop {} + } + pub fn map_or_else<U, D, F>(self, default: D, f: F) -> U + where + D: FnOnce() -> U, + F: FnOnce(T) -> U, + { + loop {} + } + // endregion:fn } } // endregion:option @@ -727,6 +764,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/src/tools/rust-analyzer/crates/toolchain/src/lib.rs b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs index 67bdad2aa..729f84a81 100644 --- a/src/tools/rust-analyzer/crates/toolchain/src/lib.rs +++ b/src/tools/rust-analyzer/crates/toolchain/src/lib.rs @@ -31,8 +31,9 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf { // example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc // 2) `<executable_name>` // example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH - // 3) `~/.cargo/bin/<executable_name>` - // example: for cargo, this tries ~/.cargo/bin/cargo + // 3) `$CARGO_HOME/bin/<executable_name>` + // where $CARGO_HOME defaults to ~/.cargo (see https://doc.rust-lang.org/cargo/guide/cargo-home.html) + // example: for cargo, this tries $CARGO_HOME/bin/cargo, or ~/.cargo/bin/cargo if $CARGO_HOME is unset. // It seems that this is a reasonable place to try for cargo, rustc, and rustup let env_var = executable_name.to_ascii_uppercase(); if let Some(path) = env::var_os(env_var) { @@ -43,8 +44,7 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf { return executable_name.into(); } - if let Some(mut path) = home::home_dir() { - path.push(".cargo"); + if let Some(mut path) = get_cargo_home() { path.push("bin"); path.push(executable_name); if let Some(path) = probe(path) { @@ -60,6 +60,19 @@ fn lookup_in_path(exec: &str) -> bool { env::split_paths(&paths).map(|path| path.join(exec)).find_map(probe).is_some() } +fn get_cargo_home() -> Option<PathBuf> { + if let Some(path) = env::var_os("CARGO_HOME") { + return Some(path.into()); + } + + if let Some(mut path) = home::home_dir() { + path.push(".cargo"); + return Some(path); + } + + None +} + fn probe(path: PathBuf) -> Option<PathBuf> { let with_extension = match env::consts::EXE_EXTENSION { "" => None, |