about summary refs log tree commit diff
diff options
context:
space:
mode:
authorhkalbasi <hamidrezakalbasi@protonmail.com>2023-12-01 16:16:46 +0330
committerhkalbasi <hamidrezakalbasi@protonmail.com>2023-12-01 16:16:46 +0330
commit4d55cac466291a2781aea2abb50f76881417e2d7 (patch)
tree0ff6b755a5319a324f34bac59621e512073b3981
parent56abc0a29c3fa541f6f4294d0908bb2b86e8071b (diff)
downloadrust-4d55cac466291a2781aea2abb50f76881417e2d7.tar.gz
rust-4d55cac466291a2781aea2abb50f76881417e2d7.zip
Initial support for implicit drop inlay hint
-rw-r--r--crates/hir-ty/src/mir.rs14
-rw-r--r--crates/hir-ty/src/mir/lower.rs54
-rw-r--r--crates/hir-ty/src/mir/pretty.rs4
-rw-r--r--crates/hir/src/lib.rs5
-rw-r--r--crates/ide/src/inlay_hints.rs9
-rw-r--r--crates/ide/src/inlay_hints/implicit_drop.rs204
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/cli/analysis_stats.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--crates/test-utils/src/minicore.rs4
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
12 files changed, 284 insertions, 25 deletions
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 747ca54858c..2e6fe59d3bd 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<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 @@ 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 9905d522146..922aee011cf 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<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 @@ impl<'ctx> MirLowerCtx<'ctx> {
         &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 @@ 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<LocalId, Vec<(&CapturedItem, usize)>> = 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 6e42bee97f7..a91f90bc249 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/src/lib.rs b/crates/hir/src/lib.rs
index 920c88ccfbc..908027a2026 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
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 24f44ca06ff..7ea9d4f1038 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 00000000000..60f1f3496f6
--- /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 aabd26da289..b54874d59f8 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/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs
index 0f6539f224d..b4debba38c9 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 f28f6ffb874..90d1d6b0555 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/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index f2ca9d82ed0..ba5c86db0e2 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<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 7091ea1ce9a..8a2d0808443 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 c43f2b964fd..cfaf4213277 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",