From 4375419b24eecc6892910557e6fbd848c27f79e7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 30 Nov 2023 15:01:11 +1100 Subject: [PATCH 1/4] Rename `HandlerInner::delay_span_bug` as `HandlerInner::span_delayed_bug`. Because the corresponding `Level` is `DelayedBug` and `span_delayed_bug` follows the pattern used everywhere else: `span_err`, `span_warning`, etc. --- crates/hir-def/src/attr/builtin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hir-def/src/attr/builtin.rs b/crates/hir-def/src/attr/builtin.rs index 2ae3cd2a939d5..15dceeb8af254 100644 --- a/crates/hir-def/src/attr/builtin.rs +++ b/crates/hir-def/src/attr/builtin.rs @@ -629,7 +629,7 @@ pub const INERT_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_attr!(TEST, rustc_regions, Normal, template!(Word), WarnFollowing), rustc_attr!( TEST, rustc_error, Normal, - template!(Word, List: "delay_span_bug_from_inside_query"), WarnFollowingWordOnly + template!(Word, List: "span_delayed_bug_from_inside_query"), WarnFollowingWordOnly ), rustc_attr!(TEST, rustc_dump_user_substs, Normal, template!(Word), WarnFollowing), rustc_attr!(TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing), From 40da288eb040ac44e6de6d670ff8155b69706080 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 30 Nov 2023 16:14:38 +1100 Subject: [PATCH 2/4] Rename `LayoutCalculator::delay_bug` as `LayoutCalculator::delayed_bug`. To match with the previous commits. --- crates/hir-ty/src/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index b2591f016d429..27c7949986878 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -110,7 +110,7 @@ struct LayoutCx<'a> { impl<'a> LayoutCalculator for LayoutCx<'a> { type TargetDataLayoutRef = &'a TargetDataLayout; - fn delay_bug(&self, txt: String) { + fn delayed_bug(&self, txt: String) { never!("{}", txt); } From 30fc9329d1c1afb023bf80bea08a243e3b6ef1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 4 Dec 2023 09:19:15 +0200 Subject: [PATCH 3/4] Merge commit 'e402c494b7c7d94a37c6d789a216187aaf9ccd3e' into sync-from-ra --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/hir-def/src/item_tree.rs | 54 ++--- crates/hir-def/src/item_tree/lower.rs | 2 +- crates/hir-ty/src/infer.rs | 21 +- crates/hir-ty/src/lower.rs | 2 +- crates/hir-ty/src/mir.rs | 14 ++ crates/hir-ty/src/mir/lower.rs | 54 +++-- crates/hir-ty/src/mir/pretty.rs | 4 +- crates/hir-ty/src/tests/patterns.rs | 24 +++ crates/hir/src/lib.rs | 30 +-- .../src/handlers/remove_parentheses.rs | 33 ++- crates/ide-completion/src/completions/dot.rs | 92 ++++++-- crates/ide-completion/src/render.rs | 116 +++++++++- crates/ide-completion/src/tests/pattern.rs | 29 +++ .../src/handlers/missing_match_arms.rs | 19 +- crates/ide/src/inlay_hints.rs | 9 +- crates/ide/src/inlay_hints/implicit_drop.rs | 204 ++++++++++++++++++ crates/ide/src/static_index.rs | 1 + crates/parser/src/grammar.rs | 10 + crates/parser/src/grammar/expressions.rs | 95 ++++---- crates/parser/src/grammar/items.rs | 1 + ..._comma_after_functional_update_syntax.rast | 66 ++++++ ...24_comma_after_functional_update_syntax.rs | 4 + .../parser/inline/err/0024_top_level_let.rast | 30 +++ .../parser/inline/err/0024_top_level_let.rs | 1 + .../rust-analyzer/src/cli/analysis_stats.rs | 1 + crates/rust-analyzer/src/config.rs | 3 + crates/rust-analyzer/src/reload.rs | 11 +- crates/test-utils/src/minicore.rs | 4 + docs/user/generated_config.adoc | 5 + editors/code/package.json | 5 + editors/code/src/debug.ts | 27 ++- editors/code/src/toolchain.ts | 14 +- 34 files changed, 821 insertions(+), 170 deletions(-) create mode 100644 crates/ide/src/inlay_hints/implicit_drop.rs create mode 100644 crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rast create mode 100644 crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rs create mode 100644 crates/parser/test_data/parser/inline/err/0024_top_level_let.rast create mode 100644 crates/parser/test_data/parser/inline/err/0024_top_level_let.rs diff --git a/Cargo.lock b/Cargo.lock index 5a8d971c3d4f8..876fd93aab713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2000,9 +2000,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" +checksum = "d0c5a71827ac326072b6405552093e2ad2accd25a32fd78d4edc82d98c7f2409" [[package]] name = "tt" diff --git a/Cargo.toml b/Cargo.toml index 73bb9c84d2c95..272f456bf9f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,7 +116,7 @@ text-size = "1.1.1" rayon = "1.8.0" serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" -triomphe = { version = "0.1.8", default-features = false, features = ["std"] } +triomphe = { version = "0.1.10", default-features = false, features = ["std"] } # can't upgrade due to dashmap depending on 0.12.3 currently hashbrown = { version = "0.12.3", features = [ "inline-more", diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs index 70b96b25739cb..473ae298c7744 100644 --- a/crates/hir-def/src/item_tree.rs +++ b/crates/hir-def/src/item_tree.rs @@ -38,7 +38,6 @@ mod tests; use std::{ fmt::{self, Debug}, hash::{Hash, Hasher}, - marker::PhantomData, ops::Index, }; @@ -340,34 +339,37 @@ pub trait ItemTreeNode: Clone { fn id_to_mod_item(id: FileItemTreeId) -> ModItem; } -pub struct FileItemTreeId { - index: Idx, - _p: PhantomData, +pub struct FileItemTreeId(Idx); + +impl FileItemTreeId { + pub fn index(&self) -> Idx { + self.0 + } } impl Clone for FileItemTreeId { fn clone(&self) -> Self { - Self { index: self.index, _p: PhantomData } + Self(self.0) } } impl Copy for FileItemTreeId {} impl PartialEq for FileItemTreeId { fn eq(&self, other: &FileItemTreeId) -> bool { - self.index == other.index + self.0 == other.0 } } impl Eq for FileItemTreeId {} impl Hash for FileItemTreeId { fn hash(&self, state: &mut H) { - self.index.hash(state) + self.0.hash(state) } } impl fmt::Debug for FileItemTreeId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.index.fmt(f) + self.0.fmt(f) } } @@ -548,7 +550,7 @@ impl Index for ItemTree { impl Index> for ItemTree { type Output = N; fn index(&self, id: FileItemTreeId) -> &N { - N::lookup(self, id.index) + N::lookup(self, id.index()) } } @@ -925,23 +927,23 @@ impl ModItem { pub fn ast_id(&self, tree: &ItemTree) -> FileAstId { match self { - ModItem::Use(it) => tree[it.index].ast_id().upcast(), - ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(), - ModItem::ExternBlock(it) => tree[it.index].ast_id().upcast(), - ModItem::Function(it) => tree[it.index].ast_id().upcast(), - ModItem::Struct(it) => tree[it.index].ast_id().upcast(), - ModItem::Union(it) => tree[it.index].ast_id().upcast(), - ModItem::Enum(it) => tree[it.index].ast_id().upcast(), - 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(), - ModItem::MacroCall(it) => tree[it.index].ast_id().upcast(), - ModItem::MacroRules(it) => tree[it.index].ast_id().upcast(), - ModItem::MacroDef(it) => tree[it.index].ast_id().upcast(), + ModItem::Use(it) => tree[it.index()].ast_id().upcast(), + ModItem::ExternCrate(it) => tree[it.index()].ast_id().upcast(), + ModItem::ExternBlock(it) => tree[it.index()].ast_id().upcast(), + ModItem::Function(it) => tree[it.index()].ast_id().upcast(), + ModItem::Struct(it) => tree[it.index()].ast_id().upcast(), + ModItem::Union(it) => tree[it.index()].ast_id().upcast(), + ModItem::Enum(it) => tree[it.index()].ast_id().upcast(), + 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(), + ModItem::MacroCall(it) => tree[it.index()].ast_id().upcast(), + ModItem::MacroRules(it) => tree[it.index()].ast_id().upcast(), + ModItem::MacroDef(it) => tree[it.index()].ast_id().upcast(), } } } diff --git a/crates/hir-def/src/item_tree/lower.rs b/crates/hir-def/src/item_tree/lower.rs index c898eb5f92112..6807326be5aef 100644 --- a/crates/hir-def/src/item_tree/lower.rs +++ b/crates/hir-def/src/item_tree/lower.rs @@ -13,7 +13,7 @@ use crate::{ use super::*; fn id(index: Idx) -> FileItemTreeId { - FileItemTreeId { index, _p: PhantomData } + FileItemTreeId(index) } pub(super) struct Ctx<'a> { diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 3d5ed1f93c0fa..8262edec22c1e 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -1152,20 +1152,15 @@ impl<'a> InferenceContext<'a> { (ty, variant) } TypeNs::TypeAliasId(it) => { - let container = it.lookup(self.db.upcast()).container; - let parent_subst = match container { - ItemContainerId::TraitId(id) => { - let subst = TyBuilder::subst_for_def(self.db, id, None) - .fill_with_inference_vars(&mut self.table) - .build(); - Some(subst) - } - // Type aliases do not exist in impls. - _ => None, + let resolved_seg = match unresolved { + None => path.segments().last().unwrap(), + Some(n) => path.segments().get(path.segments().len() - n - 1).unwrap(), }; - let ty = TyBuilder::def_ty(self.db, it.into(), parent_subst) - .fill_with_inference_vars(&mut self.table) - .build(); + let substs = + ctx.substs_from_path_segment(resolved_seg, Some(it.into()), true, None); + let ty = self.db.ty(it.into()); + let ty = self.insert_type_vars(ty.substitute(Interner, &substs)); + self.resolve_variant_on_alias(ty, unresolved, mod_path) } TypeNs::AdtSelfType(_) => { diff --git a/crates/hir-ty/src/lower.rs b/crates/hir-ty/src/lower.rs index 04005311b673b..9f5b59b239a4b 100644 --- a/crates/hir-ty/src/lower.rs +++ b/crates/hir-ty/src/lower.rs @@ -768,7 +768,7 @@ impl<'a> TyLoweringContext<'a> { } } - fn substs_from_path_segment( + pub(super) fn substs_from_path_segment( &self, segment: PathSegment<'_>, def: Option, diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 747ca54858c80..2e6fe59d3bd80 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -269,6 +269,10 @@ impl ProjectionStore { impl ProjectionId { pub const EMPTY: ProjectionId = ProjectionId(0); + pub fn is_empty(self) -> bool { + self == ProjectionId::EMPTY + } + pub fn lookup(self, store: &ProjectionStore) -> &[PlaceElem] { store.id_to_proj.get(&self).unwrap() } @@ -1069,6 +1073,10 @@ pub struct MirBody { } impl MirBody { + pub fn local_to_binding_map(&self) -> ArenaMap { + self.binding_locals.iter().map(|(it, y)| (*y, it)).collect() + } + fn walk_places(&mut self, mut f: impl FnMut(&mut Place, &mut ProjectionStore)) { fn for_operand( op: &mut Operand, @@ -1188,3 +1196,9 @@ pub enum MirSpan { } impl_from!(ExprId, PatId for MirSpan); + +impl From<&ExprId> for MirSpan { + fn from(value: &ExprId) -> Self { + (*value).into() + } +} diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 9905d522146cf..922aee011cf33 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -105,9 +105,14 @@ pub enum MirLowerError { /// A token to ensuring that each drop scope is popped at most once, thanks to the compiler that checks moves. struct DropScopeToken; impl DropScopeToken { - fn pop_and_drop(self, ctx: &mut MirLowerCtx<'_>, current: BasicBlockId) -> BasicBlockId { + fn pop_and_drop( + self, + ctx: &mut MirLowerCtx<'_>, + current: BasicBlockId, + span: MirSpan, + ) -> BasicBlockId { std::mem::forget(self); - ctx.pop_drop_scope_internal(current) + ctx.pop_drop_scope_internal(current, span) } /// It is useful when we want a drop scope is syntaxically closed, but we don't want to execute any drop @@ -582,7 +587,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.lower_loop(current, place, *label, expr_id.into(), |this, begin| { let scope = this.push_drop_scope(); if let Some((_, mut current)) = this.lower_expr_as_place(begin, *body, true)? { - current = scope.pop_and_drop(this, current); + current = scope.pop_and_drop(this, current, body.into()); this.set_goto(current, begin, expr_id.into()); } else { scope.pop_assume_dropped(this); @@ -720,7 +725,8 @@ impl<'ctx> MirLowerCtx<'ctx> { .ok_or(MirLowerError::ContinueWithoutLoop)?, }; let begin = loop_data.begin; - current = self.drop_until_scope(loop_data.drop_scope_index, current); + current = + self.drop_until_scope(loop_data.drop_scope_index, current, expr_id.into()); self.set_goto(current, begin, expr_id.into()); Ok(None) } @@ -759,7 +765,7 @@ impl<'ctx> MirLowerCtx<'ctx> { self.current_loop_blocks.as_ref().unwrap().drop_scope_index, ), }; - current = self.drop_until_scope(drop_scope, current); + current = self.drop_until_scope(drop_scope, current, expr_id.into()); self.set_goto(current, end, expr_id.into()); Ok(None) } @@ -773,7 +779,7 @@ impl<'ctx> MirLowerCtx<'ctx> { return Ok(None); } } - current = self.drop_until_scope(0, current); + current = self.drop_until_scope(0, current, expr_id.into()); self.set_terminator(current, TerminatorKind::Return, expr_id.into()); Ok(None) } @@ -1782,7 +1788,7 @@ impl<'ctx> MirLowerCtx<'ctx> { return Ok(None); }; self.push_fake_read(c, p, expr.into()); - current = scope2.pop_and_drop(self, c); + current = scope2.pop_and_drop(self, c, expr.into()); } } } @@ -1793,7 +1799,7 @@ impl<'ctx> MirLowerCtx<'ctx> { }; current = c; } - current = scope.pop_and_drop(self, current); + current = scope.pop_and_drop(self, current, span); Ok(Some(current)) } @@ -1873,9 +1879,14 @@ impl<'ctx> MirLowerCtx<'ctx> { } } - fn drop_until_scope(&mut self, scope_index: usize, mut current: BasicBlockId) -> BasicBlockId { + fn drop_until_scope( + &mut self, + scope_index: usize, + mut current: BasicBlockId, + span: MirSpan, + ) -> BasicBlockId { for scope in self.drop_scopes[scope_index..].to_vec().iter().rev() { - self.emit_drop_and_storage_dead_for_scope(scope, &mut current); + self.emit_drop_and_storage_dead_for_scope(scope, &mut current, span); } current } @@ -1891,17 +1902,22 @@ impl<'ctx> MirLowerCtx<'ctx> { } /// Don't call directly - fn pop_drop_scope_internal(&mut self, mut current: BasicBlockId) -> BasicBlockId { + fn pop_drop_scope_internal( + &mut self, + mut current: BasicBlockId, + span: MirSpan, + ) -> BasicBlockId { let scope = self.drop_scopes.pop().unwrap(); - self.emit_drop_and_storage_dead_for_scope(&scope, &mut current); + self.emit_drop_and_storage_dead_for_scope(&scope, &mut current, span); current } fn pop_drop_scope_assert_finished( &mut self, mut current: BasicBlockId, + span: MirSpan, ) -> Result { - current = self.pop_drop_scope_internal(current); + current = self.pop_drop_scope_internal(current, span); if !self.drop_scopes.is_empty() { implementation_error!("Mismatched count between drop scope push and pops"); } @@ -1912,6 +1928,7 @@ impl<'ctx> MirLowerCtx<'ctx> { &mut self, scope: &DropScope, current: &mut Idx, + span: MirSpan, ) { for &l in scope.locals.iter().rev() { if !self.result.locals[l].ty.clone().is_copy(self.db, self.owner) { @@ -1919,13 +1936,10 @@ impl<'ctx> MirLowerCtx<'ctx> { self.set_terminator( prev, TerminatorKind::Drop { place: l.into(), target: *current, unwind: None }, - MirSpan::Unknown, + span, ); } - self.push_statement( - *current, - StatementKind::StorageDead(l).with_span(MirSpan::Unknown), - ); + self.push_statement(*current, StatementKind::StorageDead(l).with_span(span)); } } } @@ -2002,7 +2016,7 @@ pub fn mir_body_for_closure_query( |_| true, )?; if let Some(current) = ctx.lower_expr_to_place(*root, return_slot().into(), current)? { - let current = ctx.pop_drop_scope_assert_finished(current)?; + let current = ctx.pop_drop_scope_assert_finished(current, root.into())?; ctx.set_terminator(current, TerminatorKind::Return, (*root).into()); } let mut upvar_map: FxHashMap> = FxHashMap::default(); @@ -2146,7 +2160,7 @@ pub fn lower_to_mir( ctx.lower_params_and_bindings([].into_iter(), binding_picker)? }; if let Some(current) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { - let current = ctx.pop_drop_scope_assert_finished(current)?; + let current = ctx.pop_drop_scope_assert_finished(current, root_expr.into())?; ctx.set_terminator(current, TerminatorKind::Return, root_expr.into()); } Ok(ctx.result) diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs index 6e42bee97f796..a91f90bc249e5 100644 --- a/crates/hir-ty/src/mir/pretty.rs +++ b/crates/hir-ty/src/mir/pretty.rs @@ -145,7 +145,7 @@ impl<'a> MirPrettyCtx<'a> { let indent = mem::take(&mut self.indent); let mut ctx = MirPrettyCtx { body: &body, - local_to_binding: body.binding_locals.iter().map(|(it, y)| (*y, it)).collect(), + local_to_binding: body.local_to_binding_map(), result, indent, ..*self @@ -167,7 +167,7 @@ impl<'a> MirPrettyCtx<'a> { } fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self { - let local_to_binding = body.binding_locals.iter().map(|(it, y)| (*y, it)).collect(); + let local_to_binding = body.local_to_binding_map(); MirPrettyCtx { body, db, diff --git a/crates/hir-ty/src/tests/patterns.rs b/crates/hir-ty/src/tests/patterns.rs index 0f5a3e1752cf5..5d7bab09c26e6 100644 --- a/crates/hir-ty/src/tests/patterns.rs +++ b/crates/hir-ty/src/tests/patterns.rs @@ -1129,3 +1129,27 @@ fn foo() { "#, ); } + +#[test] +fn generic_alias() { + check_types( + r#" +type Wrap = T; + +enum X { + A { cool: u32, stuff: u32 }, + B, +} + +fn main() { + let wrapped = Wrap::::A { + cool: 100, + stuff: 100, + }; + + if let Wrap::::A { cool, ..} = &wrapped {} + //^^^^ &u32 +} +"#, + ); +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 1bfbf7212bf0a..908027a2026d1 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -67,7 +67,7 @@ use hir_ty::{ known_const_to_ast, layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding}, method_resolution::{self, TyFingerprint}, - mir::{self, interpret_mir}, + mir::interpret_mir, primitive::UintTy, traits::FnTrait, AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg, @@ -129,9 +129,10 @@ pub use { hir_ty::{ display::{ClosureStyle, HirDisplay, HirDisplayError, HirWrite}, layout::LayoutError, - mir::MirEvalError, PointerCast, Safety, }, + // FIXME: Properly encapsulate mir + hir_ty::{mir, Interner as ChalkTyInterner}, }; // These are negative re-exports: pub using these names is forbidden, they @@ -1914,17 +1915,20 @@ impl DefWithBody { if let ast::Expr::MatchExpr(match_expr) = &source_ptr.value.to_node(&root) { - if let Some(scrut_expr) = match_expr.expr() { - acc.push( - MissingMatchArms { - scrutinee_expr: InFile::new( - source_ptr.file_id, - AstPtr::new(&scrut_expr), - ), - uncovered_patterns, - } - .into(), - ); + match match_expr.expr() { + Some(scrut_expr) if match_expr.match_arm_list().is_some() => { + acc.push( + MissingMatchArms { + scrutinee_expr: InFile::new( + source_ptr.file_id, + AstPtr::new(&scrut_expr), + ), + uncovered_patterns, + } + .into(), + ); + } + _ => {} } } } diff --git a/crates/ide-assists/src/handlers/remove_parentheses.rs b/crates/ide-assists/src/handlers/remove_parentheses.rs index ffc32f8049968..0281b29cd4279 100644 --- a/crates/ide-assists/src/handlers/remove_parentheses.rs +++ b/crates/ide-assists/src/handlers/remove_parentheses.rs @@ -1,4 +1,4 @@ -use syntax::{ast, AstNode}; +use syntax::{ast, AstNode, SyntaxKind, T}; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -39,7 +39,19 @@ pub(crate) fn remove_parentheses(acc: &mut Assists, ctx: &AssistContext<'_>) -> AssistId("remove_parentheses", AssistKind::Refactor), "Remove redundant parentheses", target, - |builder| builder.replace_ast(parens.into(), expr), + |builder| { + let prev_token = parens.syntax().first_token().and_then(|it| it.prev_token()); + let need_to_add_ws = match prev_token { + Some(it) => { + let tokens = vec![T![&], T![!], T!['('], T!['['], T!['{']]; + it.kind() != SyntaxKind::WHITESPACE && !tokens.contains(&it.kind()) + } + None => false, + }; + let expr = if need_to_add_ws { format!(" {}", expr) } else { expr.to_string() }; + + builder.replace(parens.syntax().text_range(), expr) + }, ) } @@ -49,6 +61,15 @@ mod tests { use super::*; + #[test] + fn remove_parens_space() { + check_assist( + remove_parentheses, + r#"fn f() { match$0(true) {} }"#, + r#"fn f() { match true {} }"#, + ); + } + #[test] fn remove_parens_simple() { check_assist(remove_parentheses, r#"fn f() { $0(2) + 2; }"#, r#"fn f() { 2 + 2; }"#); @@ -94,8 +115,8 @@ mod tests { check_assist(remove_parentheses, r#"fn f() { f(($02 + 2)); }"#, r#"fn f() { f(2 + 2); }"#); check_assist( remove_parentheses, - r#"fn f() { (1<2)&&$0(3>4); }"#, - r#"fn f() { (1<2)&&3>4; }"#, + r#"fn f() { (1<2) &&$0(3>4); }"#, + r#"fn f() { (1<2) && 3>4; }"#, ); } @@ -164,8 +185,8 @@ mod tests { fn remove_parens_weird_places() { check_assist( remove_parentheses, - r#"fn f() { match () { _=>$0(()) } }"#, - r#"fn f() { match () { _=>() } }"#, + r#"fn f() { match () { _ =>$0(()) } }"#, + r#"fn f() { match () { _ => () } }"#, ); check_assist( diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 5bcc867fe1810..57e06461099e5 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -26,17 +26,17 @@ pub(crate) fn complete_dot( item.add_to(acc, ctx.db); } - if let DotAccessKind::Method { .. } = dot_access.kind { - cov_mark::hit!(test_no_struct_field_completion_for_method_call); - } else { - complete_fields( - acc, - ctx, - receiver_ty, - |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty), - |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty), - ); - } + let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. }); + + complete_fields( + acc, + ctx, + receiver_ty, + |acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty), + |acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty), + is_field_access, + ); + complete_methods(ctx, receiver_ty, |func| acc.add_method(ctx, dot_access, func, None, None)); } @@ -82,6 +82,7 @@ pub(crate) fn complete_undotted_self( ) }, |acc, field, ty| acc.add_tuple_field(ctx, Some(hir::known::SELF_PARAM), field, &ty), + true, ); complete_methods(ctx, &ty, |func| { acc.add_method( @@ -104,18 +105,23 @@ fn complete_fields( receiver: &hir::Type, mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type), mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type), + is_field_access: bool, ) { let mut seen_names = FxHashSet::default(); for receiver in receiver.autoderef(ctx.db) { for (field, ty) in receiver.fields(ctx.db) { - if seen_names.insert(field.name(ctx.db)) { + if seen_names.insert(field.name(ctx.db)) + && (is_field_access || ty.is_fn() || ty.is_closure()) + { named_field(acc, field, ty); } } for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { // Tuples are always the last type in a deref chain, so just check if the name is // already seen without inserting into the hashset. - if !seen_names.contains(&hir::Name::new_tuple_field(i)) { + if !seen_names.contains(&hir::Name::new_tuple_field(i)) + && (is_field_access || ty.is_fn() || ty.is_closure()) + { // Tuple fields are always public (tuple struct fields are handled above). tuple_index(acc, i, ty); } @@ -250,7 +256,6 @@ impl A { #[test] fn test_no_struct_field_completion_for_method_call() { - cov_mark::check!(test_no_struct_field_completion_for_method_call); check( r#" struct A { the_field: u32 } @@ -1172,4 +1177,63 @@ impl> Foo { "#]], ); } + + #[test] + fn test_struct_function_field_completion() { + check( + r#" +struct S { va_field: u32, fn_field: fn() } +fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() } +"#, + expect![[r#" + fd fn_field fn() + "#]], + ); + + check_edit( + "fn_field", + r#" +struct S { va_field: u32, fn_field: fn() } +fn foo() { S { va_field: 0, fn_field: || {} }.fi$0() } +"#, + r#" +struct S { va_field: u32, fn_field: fn() } +fn foo() { (S { va_field: 0, fn_field: || {} }.fn_field)() } +"#, + ); + } + + #[test] + fn test_tuple_function_field_completion() { + check( + r#" +struct B(u32, fn()) +fn foo() { + let b = B(0, || {}); + b.$0() +} +"#, + expect![[r#" + fd 1 fn() + "#]], + ); + + check_edit( + "1", + r#" +struct B(u32, fn()) +fn foo() { + let b = B(0, || {}); + b.$0() +} +"#, + r#" +struct B(u32, fn()) +fn foo() { + let b = B(0, || {}); + (b.1)() +} +"#, + ) + } } diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index 00a9081985b83..048730c078d7b 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -18,9 +18,10 @@ use ide_db::{ RootDatabase, SnippetCap, SymbolKind, }; use syntax::{AstNode, SmolStr, SyntaxKind, TextRange}; +use text_edit::TextEdit; use crate::{ - context::{DotAccess, PathCompletionCtx, PathKind, PatternContext}, + context::{DotAccess, DotAccessKind, PathCompletionCtx, PathKind, PatternContext}, item::{Builder, CompletionRelevanceTypeMatch}, render::{ function::render_fn, @@ -147,7 +148,42 @@ pub(crate) fn render_field( .set_documentation(field.docs(db)) .set_deprecated(is_deprecated) .lookup_by(name); - item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name)); + + let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. }); + if !is_field_access || ty.is_fn() || ty.is_closure() { + let mut builder = TextEdit::builder(); + // Using TextEdit, insert '(' before the struct name and ')' before the + // dot access, then comes the field name and optionally insert function + // call parens. + + builder.replace( + ctx.source_range(), + field_with_receiver(db, receiver.as_ref(), &escaped_name).into(), + ); + + let expected_fn_type = + ctx.completion.expected_type.as_ref().is_some_and(|ty| ty.is_fn() || ty.is_closure()); + + if !expected_fn_type { + if let Some(receiver) = &dot_access.receiver { + if let Some(receiver) = ctx.completion.sema.original_ast_node(receiver.clone()) { + builder.insert(receiver.syntax().text_range().start(), "(".to_string()); + builder.insert(ctx.source_range().end(), ")".to_string()); + } + } + + let is_parens_needed = + !matches!(dot_access.kind, DotAccessKind::Method { has_parens: true }); + + if is_parens_needed { + builder.insert(ctx.source_range().end(), "()".to_string()); + } + } + + item.text_edit(builder.finish()); + } else { + item.insert_text(field_with_receiver(db, receiver.as_ref(), &escaped_name)); + } if let Some(receiver) = &dot_access.receiver { if let Some(original) = ctx.completion.sema.original_ast_node(receiver.clone()) { if let Some(ref_match) = compute_ref_match(ctx.completion, ty) { @@ -1600,7 +1636,7 @@ fn main() { fn struct_field_method_ref() { check_kinds( r#" -struct Foo { bar: u32 } +struct Foo { bar: u32, qux: fn() } impl Foo { fn baz(&self) -> u32 { 0 } } fn foo(f: Foo) { let _: &u32 = f.b$0 } @@ -1610,30 +1646,92 @@ fn foo(f: Foo) { let _: &u32 = f.b$0 } [ CompletionItem { label: "baz()", - source_range: 98..99, - delete: 98..99, + source_range: 109..110, + delete: 109..110, insert: "baz()$0", kind: Method, lookup: "baz", detail: "fn(&self) -> u32", - ref_match: "&@96", + ref_match: "&@107", }, CompletionItem { label: "bar", - source_range: 98..99, - delete: 98..99, + source_range: 109..110, + delete: 109..110, insert: "bar", kind: SymbolKind( Field, ), detail: "u32", - ref_match: "&@96", + ref_match: "&@107", + }, + CompletionItem { + label: "qux", + source_range: 109..110, + text_edit: TextEdit { + indels: [ + Indel { + insert: "(", + delete: 107..107, + }, + Indel { + insert: "qux)()", + delete: 109..110, + }, + ], + }, + kind: SymbolKind( + Field, + ), + detail: "fn()", }, ] "#]], ); } + #[test] + fn expected_fn_type_ref() { + check_kinds( + r#" +struct S { field: fn() } + +fn foo() { + let foo: fn() = S { fields: || {}}.fi$0; +} +"#, + &[CompletionItemKind::SymbolKind(SymbolKind::Field)], + expect![[r#" + [ + CompletionItem { + label: "field", + source_range: 76..78, + delete: 76..78, + insert: "field", + kind: SymbolKind( + Field, + ), + detail: "fn()", + relevance: CompletionRelevance { + exact_name_match: false, + type_match: Some( + Exact, + ), + is_local: false, + is_item_from_trait: false, + is_name_already_imported: false, + requires_import: false, + is_op_method: false, + is_private_editable: false, + postfix_match: None, + is_definite: false, + }, + }, + ] + "#]], + ) + } + #[test] fn qualified_path_ref() { check_kinds( diff --git a/crates/ide-completion/src/tests/pattern.rs b/crates/ide-completion/src/tests/pattern.rs index 8af6cce98f635..b2e8274a84d7d 100644 --- a/crates/ide-completion/src/tests/pattern.rs +++ b/crates/ide-completion/src/tests/pattern.rs @@ -354,6 +354,35 @@ fn outer(Foo { bar$0 }: Foo) {} ) } +#[test] +fn completes_in_record_field_pat_with_generic_type_alias() { + check_empty( + r#" +type Wrap = T; + +enum X { + A { cool: u32, stuff: u32 }, + B, +} + +fn main() { + let wrapped = Wrap::::A { + cool: 100, + stuff: 100, + }; + + if let Wrap::::A { $0 } = &wrapped {}; +} +"#, + expect![[r#" + fd cool u32 + fd stuff u32 + kw mut + kw ref + "#]], + ) +} + #[test] fn completes_in_fn_param() { check_empty( diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs index 84267d3d9069f..ef6a273ed8e65 100644 --- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs +++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs @@ -17,7 +17,10 @@ pub(crate) fn missing_match_arms( #[cfg(test)] mod tests { - use crate::tests::check_diagnostics; + use crate::{ + tests::{check_diagnostics, check_diagnostics_with_config}, + DiagnosticsConfig, + }; #[track_caller] fn check_diagnostics_no_bails(ra_fixture: &str) { @@ -25,6 +28,20 @@ mod tests { crate::tests::check_diagnostics(ra_fixture) } + #[test] + fn empty_body() { + let mut config = DiagnosticsConfig::test_sample(); + config.disabled.insert("syntax-error".to_string()); + check_diagnostics_with_config( + config, + r#" +fn main() { + match 0; +} +"#, + ); + } + #[test] fn empty_tuple() { check_diagnostics_no_bails( diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index 24f44ca06ffb3..7ea9d4f1038b4 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs @@ -31,6 +31,7 @@ mod discriminant; mod fn_lifetime_fn; mod implicit_static; mod param_name; +mod implicit_drop; #[derive(Clone, Debug, PartialEq, Eq)] pub struct InlayHintsConfig { @@ -45,6 +46,7 @@ pub struct InlayHintsConfig { pub closure_return_type_hints: ClosureReturnTypeHints, pub closure_capture_hints: bool, pub binding_mode_hints: bool, + pub implicit_drop_hints: bool, pub lifetime_elision_hints: LifetimeElisionHints, pub param_names_for_lifetime_elision_hints: bool, pub hide_named_constructor_hints: bool, @@ -124,6 +126,7 @@ pub enum InlayKind { Lifetime, Parameter, Type, + Drop, } #[derive(Debug)] @@ -503,7 +506,10 @@ fn hints( ast::Item(it) => match it { // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints ast::Item::Impl(_) => None, - ast::Item::Fn(it) => fn_lifetime_fn::hints(hints, config, it), + ast::Item::Fn(it) => { + implicit_drop::hints(hints, sema, config, &it); + fn_lifetime_fn::hints(hints, config, it) + }, // static type elisions ast::Item::Static(it) => implicit_static::hints(hints, config, Either::Left(it)), ast::Item::Const(it) => implicit_static::hints(hints, config, Either::Right(it)), @@ -591,6 +597,7 @@ mod tests { max_length: None, closing_brace_hints_min_lines: None, fields_to_resolve: InlayFieldsToResolve::empty(), + implicit_drop_hints: false, }; pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig { type_hints: true, diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs new file mode 100644 index 0000000000000..60f1f3496f6cd --- /dev/null +++ b/crates/ide/src/inlay_hints/implicit_drop.rs @@ -0,0 +1,204 @@ +//! Implementation of "implicit drop" inlay hints: +//! ```no_run +//! fn main() { +//! let x = vec![2]; +//! if some_condition() { +//! /* drop(x) */return; +//! } +//! } +//! ``` +use hir::{ + db::{DefDatabase as _, HirDatabase as _}, + mir::{MirSpan, TerminatorKind}, + ChalkTyInterner, DefWithBody, Semantics, +}; +use ide_db::{base_db::FileRange, RootDatabase}; + +use syntax::{ + ast::{self, AstNode}, + match_ast, +}; + +use crate::{InlayHint, InlayHintLabel, InlayHintPosition, InlayHintsConfig, InlayKind}; + +pub(super) fn hints( + acc: &mut Vec, + sema: &Semantics<'_, RootDatabase>, + config: &InlayHintsConfig, + def: &ast::Fn, +) -> Option<()> { + if !config.implicit_drop_hints { + return None; + } + + let def = sema.to_def(def)?; + let def: DefWithBody = def.into(); + + let source_map = sema.db.body_with_source_map(def.into()).1; + + let hir = sema.db.body(def.into()); + let mir = sema.db.mir_body(def.into()).ok()?; + + let local_to_binding = mir.local_to_binding_map(); + + for (_, bb) in mir.basic_blocks.iter() { + let terminator = bb.terminator.as_ref()?; + if let TerminatorKind::Drop { place, .. } = terminator.kind { + if !place.projection.is_empty() { + continue; // Ignore complex cases for now + } + if mir.locals[place.local].ty.adt_id(ChalkTyInterner).is_none() { + continue; // Arguably only ADTs have significant drop impls + } + let Some(binding) = local_to_binding.get(place.local) else { + continue; // Ignore temporary values + }; + let range = match terminator.span { + MirSpan::ExprId(e) => match source_map.expr_syntax(e) { + Ok(s) => { + let root = &s.file_syntax(sema.db); + let expr = s.value.to_node(root); + let expr = expr.syntax(); + match_ast! { + match expr { + ast::BlockExpr(x) => x.stmt_list().and_then(|x| x.r_curly_token()).map(|x| x.text_range()).unwrap_or_else(|| expr.text_range()), + _ => expr.text_range(), + } + } + } + Err(_) => continue, + }, + MirSpan::PatId(p) => match source_map.pat_syntax(p) { + Ok(s) => s.value.text_range(), + Err(_) => continue, + }, + MirSpan::Unknown => continue, + }; + let binding = &hir.bindings[*binding]; + let binding_source = binding + .definitions + .first() + .and_then(|d| source_map.pat_syntax(*d).ok()) + .and_then(|d| { + Some(FileRange { file_id: d.file_id.file_id()?, range: d.value.text_range() }) + }); + let name = binding.name.to_smol_str(); + if name.starts_with(" Option<()> { + let x = X; + let t_opt = Some(2); + let t = t_opt?; + //^^^^^^ drop(x) + Some(()) + } + //^ drop(x) +"#, + ); + } + + #[test] + fn if_let() { + check_with_config( + ONLY_DROP_CONFIG, + r#" + struct X; + fn f() { + let x = X; + if let X = x { + let y = X; + } + //^ drop(y) + } + //^ drop(x) +"#, + ); + } +} diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index aabd26da289ca..b54874d59f8bc 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -118,6 +118,7 @@ impl StaticIndex<'_> { adjustment_hints: crate::AdjustmentHints::Never, adjustment_hints_mode: AdjustmentHintsMode::Prefix, adjustment_hints_hide_outside_unsafe: false, + implicit_drop_hints: false, hide_named_constructor_hints: false, hide_closure_initialization_hints: false, closure_style: hir::ClosureStyle::ImplFn, diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs index 6a2a9adce15cb..19da297b58ccc 100644 --- a/crates/parser/src/grammar.rs +++ b/crates/parser/src/grammar.rs @@ -376,6 +376,16 @@ fn error_block(p: &mut Parser<'_>, message: &str) { m.complete(p, ERROR); } +// test_err top_level_let +// let ref foo: fn() = 1 + 3; +fn error_let_stmt(p: &mut Parser<'_>, message: &str) { + assert!(p.at(T![let])); + let m = p.start(); + p.error(message); + expressions::let_stmt(p, expressions::Semicolon::Optional); + m.complete(p, ERROR); +} + /// The `parser` passed this is required to at least consume one token if it returns `true`. /// If the `parser` returns false, parsing will stop. fn delimited( diff --git a/crates/parser/src/grammar/expressions.rs b/crates/parser/src/grammar/expressions.rs index 1cbd1663230bd..e346ece2f94b6 100644 --- a/crates/parser/src/grammar/expressions.rs +++ b/crates/parser/src/grammar/expressions.rs @@ -59,7 +59,8 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) { attributes::outer_attrs(p); if p.at(T![let]) { - let_stmt(p, m, semicolon); + let_stmt(p, semicolon); + m.complete(p, LET_STMT); return; } @@ -109,54 +110,53 @@ pub(super) fn stmt(p: &mut Parser<'_>, semicolon: Semicolon) { m.complete(p, EXPR_STMT); } } +} - // test let_stmt - // fn f() { let x: i32 = 92; } - fn let_stmt(p: &mut Parser<'_>, m: Marker, with_semi: Semicolon) { - p.bump(T![let]); - patterns::pattern(p); - if p.at(T![:]) { - // test let_stmt_ascription - // fn f() { let x: i32; } - types::ascription(p); - } +// test let_stmt +// fn f() { let x: i32 = 92; } +pub(super) fn let_stmt(p: &mut Parser<'_>, with_semi: Semicolon) { + p.bump(T![let]); + patterns::pattern(p); + if p.at(T![:]) { + // test let_stmt_ascription + // fn f() { let x: i32; } + types::ascription(p); + } - let mut expr_after_eq: Option = None; - if p.eat(T![=]) { - // test let_stmt_init - // fn f() { let x = 92; } - expr_after_eq = expressions::expr(p); - } + let mut expr_after_eq: Option = None; + if p.eat(T![=]) { + // test let_stmt_init + // fn f() { let x = 92; } + 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", - ) - } + 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); - m.complete(p, LET_ELSE); } - match with_semi { - Semicolon::Forbidden => (), - Semicolon::Optional => { - p.eat(T![;]); - } - Semicolon::Required => { - p.expect(T![;]); - } + // test let_else + // fn f() { let Some(x) = opt else { return }; } + let m = p.start(); + p.bump(T![else]); + block_expr(p); + m.complete(p, LET_ELSE); + } + + match with_semi { + Semicolon::Forbidden => (), + Semicolon::Optional => { + p.eat(T![;]); + } + Semicolon::Required => { + p.expect(T![;]); } - m.complete(p, LET_STMT); } } @@ -693,6 +693,17 @@ pub(crate) fn record_expr_field_list(p: &mut Parser<'_>) { // We permit `.. }` on the left-hand side of a destructuring assignment. if !p.at(T!['}']) { expr(p); + + if p.at(T![,]) { + // test_err comma_after_functional_update_syntax + // fn foo() { + // S { ..x, }; + // S { ..x, a: 0 } + // } + + // Do not bump, so we can support additional fields after this comma. + p.error("cannot use a comma after the base struct"); + } } } T!['{'] => { diff --git a/crates/parser/src/grammar/items.rs b/crates/parser/src/grammar/items.rs index 4e850b1f74df8..34fd3420f1c99 100644 --- a/crates/parser/src/grammar/items.rs +++ b/crates/parser/src/grammar/items.rs @@ -79,6 +79,7 @@ pub(super) fn item_or_macro(p: &mut Parser<'_>, stop_on_r_curly: bool) { e.complete(p, ERROR); } EOF | T!['}'] => p.error("expected an item"), + T![let] => error_let_stmt(p, "expected an item"), _ => p.err_and_bump("expected an item"), } } diff --git a/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rast b/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rast new file mode 100644 index 0000000000000..0e2fe5988d6aa --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rast @@ -0,0 +1,66 @@ +SOURCE_FILE + FN + FN_KW "fn" + WHITESPACE " " + NAME + IDENT "foo" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BLOCK_EXPR + STMT_LIST + L_CURLY "{" + WHITESPACE "\n " + EXPR_STMT + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + COMMA "," + WHITESPACE " " + R_CURLY "}" + SEMICOLON ";" + WHITESPACE "\n " + RECORD_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "S" + WHITESPACE " " + RECORD_EXPR_FIELD_LIST + L_CURLY "{" + WHITESPACE " " + DOT2 ".." + PATH_EXPR + PATH + PATH_SEGMENT + NAME_REF + IDENT "x" + COMMA "," + WHITESPACE " " + RECORD_EXPR_FIELD + NAME_REF + IDENT "a" + COLON ":" + WHITESPACE " " + LITERAL + INT_NUMBER "0" + WHITESPACE " " + R_CURLY "}" + WHITESPACE "\n" + R_CURLY "}" + WHITESPACE "\n" +error 22: cannot use a comma after the base struct +error 38: cannot use a comma after the base struct diff --git a/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rs b/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rs new file mode 100644 index 0000000000000..14cf96719b464 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0024_comma_after_functional_update_syntax.rs @@ -0,0 +1,4 @@ +fn foo() { + S { ..x, }; + S { ..x, a: 0 } +} diff --git a/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast new file mode 100644 index 0000000000000..5ddef5f3f03f6 --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rast @@ -0,0 +1,30 @@ +SOURCE_FILE + ERROR + LET_KW "let" + WHITESPACE " " + IDENT_PAT + REF_KW "ref" + WHITESPACE " " + NAME + IDENT "foo" + COLON ":" + WHITESPACE " " + FN_PTR_TYPE + FN_KW "fn" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + EQ "=" + WHITESPACE " " + BIN_EXPR + LITERAL + INT_NUMBER "1" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + LITERAL + INT_NUMBER "3" + SEMICOLON ";" + WHITESPACE "\n" +error 0: expected an item diff --git a/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs new file mode 100644 index 0000000000000..3d3e7dd56c71c --- /dev/null +++ b/crates/parser/test_data/parser/inline/err/0024_top_level_let.rs @@ -0,0 +1 @@ +let ref foo: fn() = 1 + 3; diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 0f6539f224d79..b4debba38c974 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -783,6 +783,7 @@ impl flags::AnalysisStats { closure_return_type_hints: ide::ClosureReturnTypeHints::Always, closure_capture_hints: true, binding_mode_hints: true, + implicit_drop_hints: true, lifetime_elision_hints: ide::LifetimeElisionHints::Always, param_names_for_lifetime_elision_hints: true, hide_named_constructor_hints: false, diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index f28f6ffb8748a..90d1d6b055594 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -381,6 +381,8 @@ config_data! { inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false", /// Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). inlayHints_expressionAdjustmentHints_mode: AdjustmentHintsModeDef = "\"prefix\"", + /// Whether to show implicit drop hints. + inlayHints_implicitDrops_enable: bool = "false", /// Whether to show inlay type hints for elided lifetimes in function signatures. inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"", /// Whether to prefer using parameter names as the name for elided lifetime hints if possible. @@ -1391,6 +1393,7 @@ impl Config { type_hints: self.data.inlayHints_typeHints_enable, parameter_hints: self.data.inlayHints_parameterHints_enable, chaining_hints: self.data.inlayHints_chainingHints_enable, + implicit_drop_hints: self.data.inlayHints_implicitDrops_enable, discriminant_hints: match self.data.inlayHints_discriminantHints_enable { DiscriminantHintsDef::Always => ide::DiscriminantHints::Always, DiscriminantHintsDef::Never => ide::DiscriminantHints::Never, diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs index 8dba83ed5edd2..abe2191f40024 100644 --- a/crates/rust-analyzer/src/reload.rs +++ b/crates/rust-analyzer/src/reload.rs @@ -22,6 +22,7 @@ use ide_db::{ base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, ProcMacros}, FxHashMap, }; +use itertools::Itertools; use load_cargo::{load_proc_macro, ProjectFolders}; use proc_macro_api::ProcMacroServer; use project_model::{ProjectWorkspace, WorkspaceBuildScripts}; @@ -227,16 +228,12 @@ impl GlobalState { let mut i = 0; while i < workspaces.len() { if let Ok(w) = &workspaces[i] { - let dupes: Vec<_> = workspaces + let dupes: Vec<_> = workspaces[i + 1..] .iter() - .enumerate() - .skip(i + 1) - .filter_map(|(i, it)| { - it.as_ref().ok().filter(|ws| ws.eq_ignore_build_data(w)).map(|_| i) - }) + .positions(|it| it.as_ref().is_ok_and(|ws| ws.eq_ignore_build_data(w))) .collect(); dupes.into_iter().rev().for_each(|d| { - _ = workspaces.remove(d); + _ = workspaces.remove(d + i + 1); }); } i += 1; diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index f2ca9d82ed0dd..ba5c86db0e2ea 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -1054,6 +1054,10 @@ pub mod option { Some(T), } + // region:copy + impl Copy for Option {} + // endregion:copy + impl Option { pub const fn unwrap(self) -> T { match self { diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 7091ea1ce9a7e..8a2d080844352 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -564,6 +564,11 @@ Whether to hide inlay hints for type adjustments outside of `unsafe` blocks. -- Whether to show inlay hints as postfix ops (`.*` instead of `*`, etc). -- +[[rust-analyzer.inlayHints.implicitDrops.enable]]rust-analyzer.inlayHints.implicitDrops.enable (default: `false`):: ++ +-- +Whether to show implicit drop hints. +-- [[rust-analyzer.inlayHints.lifetimeElisionHints.enable]]rust-analyzer.inlayHints.lifetimeElisionHints.enable (default: `"never"`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index c43f2b964fde9..cfaf421327753 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1264,6 +1264,11 @@ "Show prefix or postfix depending on which uses less parenthesis, preferring postfix." ] }, + "rust-analyzer.inlayHints.implicitDrops.enable": { + "markdownDescription": "Whether to show implicit drop hints.", + "default": false, + "type": "boolean" + }, "rust-analyzer.inlayHints.lifetimeElisionHints.enable": { "markdownDescription": "Whether to show inlay type hints for elided lifetimes in function signatures.", "default": "never", diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index e817d680eaeff..06034e1648077 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -3,7 +3,7 @@ import * as vscode from "vscode"; import * as path from "path"; import type * as ra from "./lsp_ext"; -import { Cargo, getRustcId, getSysroot } from "./toolchain"; +import { Cargo, type ExecutableInfo, getRustcId, getSysroot } from "./toolchain"; import type { Ctx } from "./ctx"; import { prepareEnv } from "./run"; import { unwrapUndefinable } from "./undefinable"; @@ -12,6 +12,7 @@ const debugOutput = vscode.window.createOutputChannel("Debug"); type DebugConfigProvider = ( config: ra.Runnable, executable: string, + cargoWorkspace: string, env: Record, sourceFileMap?: Record, ) => vscode.DebugConfiguration; @@ -130,7 +131,7 @@ async function getDebugConfiguration( } const env = prepareEnv(runnable, ctx.config.runnablesExtraEnv); - const executable = await getDebugExecutable(runnable, env); + const { executable, workspace: cargoWorkspace } = await getDebugExecutableInfo(runnable, env); let sourceFileMap = debugOptions.sourceFileMap; if (sourceFileMap === "auto") { // let's try to use the default toolchain @@ -142,7 +143,13 @@ async function getDebugConfiguration( } const provider = unwrapUndefinable(knownEngines[debugEngine.id]); - const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap); + const debugConfig = provider( + runnable, + simplifyPath(executable), + cargoWorkspace, + env, + sourceFileMap, + ); if (debugConfig.type in debugOptions.engineSettings) { const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; for (var key in settingsMap) { @@ -164,20 +171,21 @@ async function getDebugConfiguration( return debugConfig; } -async function getDebugExecutable( +async function getDebugExecutableInfo( runnable: ra.Runnable, env: Record, -): Promise { +): Promise { const cargo = new Cargo(runnable.args.workspaceRoot || ".", debugOutput, env); - const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); + const executableInfo = await cargo.executableInfoFromArgs(runnable.args.cargoArgs); // if we are here, there were no compilation errors. - return executable; + return executableInfo; } function getLldbDebugConfig( runnable: ra.Runnable, executable: string, + cargoWorkspace: string, env: Record, sourceFileMap?: Record, ): vscode.DebugConfiguration { @@ -187,7 +195,7 @@ function getLldbDebugConfig( name: runnable.label, program: executable, args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot, + cwd: cargoWorkspace || runnable.args.workspaceRoot, sourceMap: sourceFileMap, sourceLanguages: ["rust"], env, @@ -197,6 +205,7 @@ function getLldbDebugConfig( function getCppvsDebugConfig( runnable: ra.Runnable, executable: string, + cargoWorkspace: string, env: Record, sourceFileMap?: Record, ): vscode.DebugConfiguration { @@ -206,7 +215,7 @@ function getCppvsDebugConfig( name: runnable.label, program: executable, args: runnable.args.executableArgs, - cwd: runnable.args.workspaceRoot, + cwd: cargoWorkspace || runnable.args.workspaceRoot, sourceFileMap, env, }; diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts index 58e5fc747a190..1037e513aa106 100644 --- a/editors/code/src/toolchain.ts +++ b/editors/code/src/toolchain.ts @@ -9,11 +9,17 @@ import { unwrapUndefinable } from "./undefinable"; interface CompilationArtifact { fileName: string; + workspace: string; name: string; kind: string; isTest: boolean; } +export interface ExecutableInfo { + executable: string; + workspace: string; +} + export interface ArtifactSpec { cargoArgs: string[]; filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[]; @@ -68,6 +74,7 @@ export class Cargo { artifacts.push({ fileName: message.executable, name: message.target.name, + workspace: message.manifest_path.replace(/\/Cargo\.toml$/, ""), kind: message.target.kind[0], isTest: message.profile.test, }); @@ -86,7 +93,7 @@ export class Cargo { return spec.filter?.(artifacts) ?? artifacts; } - async executableFromArgs(args: readonly string[]): Promise { + async executableInfoFromArgs(args: readonly string[]): Promise { const artifacts = await this.getArtifacts(Cargo.artifactSpec(args)); if (artifacts.length === 0) { @@ -96,7 +103,10 @@ export class Cargo { } const artifact = unwrapUndefinable(artifacts[0]); - return artifact.fileName; + return { + executable: artifact.fileName, + workspace: artifact.workspace, + }; } private async runCargo( From 6d2543b62256676718f1936a182f92b10f2ca8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauren=C8=9Biu=20Nicola?= Date: Mon, 4 Dec 2023 21:41:19 +0200 Subject: [PATCH 4/4] Temporarily revert delay_bug to delayed_bug change --- crates/hir-ty/src/layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/hir-ty/src/layout.rs b/crates/hir-ty/src/layout.rs index 27c7949986878..b2591f016d429 100644 --- a/crates/hir-ty/src/layout.rs +++ b/crates/hir-ty/src/layout.rs @@ -110,7 +110,7 @@ struct LayoutCx<'a> { impl<'a> LayoutCalculator for LayoutCx<'a> { type TargetDataLayoutRef = &'a TargetDataLayout; - fn delayed_bug(&self, txt: String) { + fn delay_bug(&self, txt: String) { never!("{}", txt); }