Auto merge of #15879 - dfireBird:fix-14656, r=Veykril
Implement completion for the callable fields.
Fixes #14656
PR is opened with basic changes. It could be improved by having a new `SymbolKind` for the callable fields and implementing a separate render function similar to the `render_method` for the new `SymbolKind`.
It could also be done without any changes to the `SymbolKind` of course, have the new function called based on the type of field.
I prefer the former method.
Please give any thoughts or changes you think is appropriate for this method. I could start working on that in this same PR.
diff --git a/crates/hir-def/src/item_tree.rs b/crates/hir-def/src/item_tree.rs
index 70b96b2..473ae29 100644
--- a/crates/hir-def/src/item_tree.rs
+++ b/crates/hir-def/src/item_tree.rs
@@ -38,7 +38,6 @@
use std::{
fmt::{self, Debug},
hash::{Hash, Hasher},
- marker::PhantomData,
ops::Index,
};
@@ -340,34 +339,37 @@
fn id_to_mod_item(id: FileItemTreeId<Self>) -> ModItem;
}
-pub struct FileItemTreeId<N: ItemTreeNode> {
- index: Idx<N>,
- _p: PhantomData<N>,
+pub struct FileItemTreeId<N: ItemTreeNode>(Idx<N>);
+
+impl<N: ItemTreeNode> FileItemTreeId<N> {
+ pub fn index(&self) -> Idx<N> {
+ self.0
+ }
}
impl<N: ItemTreeNode> Clone for FileItemTreeId<N> {
fn clone(&self) -> Self {
- Self { index: self.index, _p: PhantomData }
+ Self(self.0)
}
}
impl<N: ItemTreeNode> Copy for FileItemTreeId<N> {}
impl<N: ItemTreeNode> PartialEq for FileItemTreeId<N> {
fn eq(&self, other: &FileItemTreeId<N>) -> bool {
- self.index == other.index
+ self.0 == other.0
}
}
impl<N: ItemTreeNode> Eq for FileItemTreeId<N> {}
impl<N: ItemTreeNode> Hash for FileItemTreeId<N> {
fn hash<H: Hasher>(&self, state: &mut H) {
- self.index.hash(state)
+ self.0.hash(state)
}
}
impl<N: ItemTreeNode> fmt::Debug for FileItemTreeId<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- self.index.fmt(f)
+ self.0.fmt(f)
}
}
@@ -548,7 +550,7 @@
impl<N: ItemTreeNode> Index<FileItemTreeId<N>> for ItemTree {
type Output = N;
fn index(&self, id: FileItemTreeId<N>) -> &N {
- N::lookup(self, id.index)
+ N::lookup(self, id.index())
}
}
@@ -925,23 +927,23 @@
pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> {
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 c898eb5..6807326 100644
--- a/crates/hir-def/src/item_tree/lower.rs
+++ b/crates/hir-def/src/item_tree/lower.rs
@@ -13,7 +13,7 @@
use super::*;
fn id<N: ItemTreeNode>(index: Idx<N>) -> FileItemTreeId<N> {
- FileItemTreeId { index, _p: PhantomData }
+ FileItemTreeId(index)
}
pub(super) struct Ctx<'a> {
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 747ca54..2e6fe59 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -269,6 +269,10 @@
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 @@
}
impl MirBody {
+ pub fn local_to_binding_map(&self) -> ArenaMap<LocalId, BindingId> {
+ 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 @@
}
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 9905d52..922aee0 100644
--- a/crates/hir-ty/src/mir/lower.rs
+++ b/crates/hir-ty/src/mir/lower.rs
@@ -105,9 +105,14 @@
/// 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 @@
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 @@
.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 @@
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 @@
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 @@
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 @@
};
current = c;
}
- current = scope.pop_and_drop(self, current);
+ current = scope.pop_and_drop(self, current, span);
Ok(Some(current))
}
@@ -1873,9 +1879,14 @@
}
}
- 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 @@
}
/// 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<BasicBlockId> {
- 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 @@
&mut self,
scope: &DropScope,
current: &mut Idx<BasicBlock>,
+ 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 @@
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 @@
|_| 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<LocalId, Vec<(&CapturedItem, usize)>> = FxHashMap::default();
@@ -2146,7 +2160,7 @@
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 6e42bee..a91f90b 100644
--- a/crates/hir-ty/src/mir/pretty.rs
+++ b/crates/hir-ty/src/mir/pretty.rs
@@ -145,7 +145,7 @@
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 @@
}
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/src/lib.rs b/crates/hir/src/lib.rs
index 1bfbf72..908027a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -67,7 +67,7 @@
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 @@
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 @@
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-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 84267d3..ef6a273 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 @@
#[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) {
@@ -26,6 +29,20 @@
}
#[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(
r#"
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 24f44ca..7ea9d4f 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -31,6 +31,7 @@
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 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 @@
Lifetime,
Parameter,
Type,
+ Drop,
}
#[derive(Debug)]
@@ -503,7 +506,10 @@
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 @@
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 0000000..60f1f34
--- /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<InlayHint>,
+ 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("<ra@") {
+ continue; // Ignore desugared variables
+ }
+ let mut label = InlayHintLabel::simple(
+ name,
+ Some(crate::InlayTooltip::String("moz".into())),
+ binding_source,
+ );
+ label.prepend_str("drop(");
+ label.append_str(")");
+ acc.push(InlayHint {
+ range,
+ position: InlayHintPosition::Before,
+ pad_left: true,
+ pad_right: true,
+ kind: InlayKind::Drop,
+ needs_resolve: label.needs_resolve(),
+ label,
+ text_edit: None,
+ })
+ }
+ }
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+ InlayHintsConfig,
+ };
+
+ const ONLY_DROP_CONFIG: InlayHintsConfig =
+ InlayHintsConfig { implicit_drop_hints: true, ..DISABLED_CONFIG };
+
+ #[test]
+ fn basic() {
+ check_with_config(
+ ONLY_DROP_CONFIG,
+ r#"
+ struct X;
+ fn f() {
+ let x = X;
+ if 2 == 5 {
+ return;
+ //^^^^^^ drop(x)
+ }
+ }
+ //^ drop(x)
+"#,
+ );
+ }
+
+ #[test]
+ fn no_hint_for_copy_types_and_mutable_references() {
+ // `T: Copy` and `T = &mut U` types do nothing on drop, so we should hide drop inlay hint for them.
+ check_with_config(
+ ONLY_DROP_CONFIG,
+ r#"
+//- minicore: copy, derive
+
+ struct X(i32, i32);
+ #[derive(Clone, Copy)]
+ struct Y(i32, i32);
+ fn f() {
+ let a = 2;
+ let b = a + 4;
+ let mut x = X(a, b);
+ let mut y = Y(a, b);
+ let mx = &mut x;
+ let my = &mut y;
+ let c = a + b;
+ }
+ //^ drop(x)
+"#,
+ );
+ }
+
+ #[test]
+ fn try_operator() {
+ // We currently show drop inlay hint for every `?` operator that may potentialy drop something. We probably need to
+ // make it configurable as it doesn't seem very useful.
+ check_with_config(
+ ONLY_DROP_CONFIG,
+ r#"
+//- minicore: copy, try, option
+
+ struct X;
+ fn f() -> 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 aabd26d..b54874d 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -118,6 +118,7 @@
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 6a2a9ad..19da297 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -376,6 +376,16 @@
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 1cbd166..e346ece 100644
--- a/crates/parser/src/grammar/expressions.rs
+++ b/crates/parser/src/grammar/expressions.rs
@@ -59,7 +59,8 @@
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 @@
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<CompletedMarker> = 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<CompletedMarker> = 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",
- )
- }
- }
-
- // 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![;]);
+ 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",
+ )
}
}
- m.complete(p, LET_STMT);
+
+ // 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![;]);
+ }
}
}
@@ -693,6 +693,17 @@
// 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 4e850b1..34fd342 100644
--- a/crates/parser/src/grammar/items.rs
+++ b/crates/parser/src/grammar/items.rs
@@ -79,6 +79,7 @@
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 0000000..0e2fe59
--- /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 0000000..14cf967
--- /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 0000000..5ddef5f
--- /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 0000000..3d3e7dd
--- /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 0f6539f..b4debba 100644
--- a/crates/rust-analyzer/src/cli/analysis_stats.rs
+++ b/crates/rust-analyzer/src/cli/analysis_stats.rs
@@ -783,6 +783,7 @@
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 f28f6ff..90d1d6b 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -381,6 +381,8 @@
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 @@
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/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index f2ca9d8..ba5c86d 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -1054,6 +1054,10 @@
Some(T),
}
+ // region:copy
+ impl<T: Copy> Copy for Option<T> {}
+ // endregion:copy
+
impl<T> Option<T> {
pub const fn unwrap(self) -> T {
match self {
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 7091ea1..8a2d080 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -564,6 +564,11 @@
--
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 c43f2b9..cfaf421 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 e817d68..06034e1 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -3,7 +3,7 @@
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 @@
type DebugConfigProvider = (
config: ra.Runnable,
executable: string,
+ cargoWorkspace: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>,
) => vscode.DebugConfiguration;
@@ -130,7 +131,7 @@
}
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 @@
}
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 @@
return debugConfig;
}
-async function getDebugExecutable(
+async function getDebugExecutableInfo(
runnable: ra.Runnable,
env: Record<string, string>,
-): Promise<string> {
+): Promise<ExecutableInfo> {
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<string, string>,
sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration {
@@ -187,7 +195,7 @@
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 getCppvsDebugConfig(
runnable: ra.Runnable,
executable: string,
+ cargoWorkspace: string,
env: Record<string, string>,
sourceFileMap?: Record<string, string>,
): vscode.DebugConfiguration {
@@ -206,7 +215,7 @@
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 58e5fc7..1037e51 100644
--- a/editors/code/src/toolchain.ts
+++ b/editors/code/src/toolchain.ts
@@ -9,11 +9,17 @@
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 @@
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 @@
return spec.filter?.(artifacts) ?? artifacts;
}
- async executableFromArgs(args: readonly string[]): Promise<string> {
+ async executableInfoFromArgs(args: readonly string[]): Promise<ExecutableInfo> {
const artifacts = await this.getArtifacts(Cargo.artifactSpec(args));
if (artifacts.length === 0) {
@@ -96,7 +103,10 @@
}
const artifact = unwrapUndefinable(artifacts[0]);
- return artifact.fileName;
+ return {
+ executable: artifact.fileName,
+ workspace: artifact.workspace,
+ };
}
private async runCargo(