about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-05-16 11:16:22 +0000
committerbors <bors@rust-lang.org>2022-05-16 11:16:22 +0000
commit1dc25e51a0d7af35809187bf1eb8e0bd9386fc11 (patch)
treea124c31df777fbec086b997272eeee38eae95e49
parentac4ce4259b906e7998695722b5af4cf2f261b250 (diff)
parent977f0ba968203918b135c3d99380db9f9a5c2c19 (diff)
downloadrust-1dc25e51a0d7af35809187bf1eb8e0bd9386fc11.tar.gz
rust-1dc25e51a0d7af35809187bf1eb8e0bd9386fc11.zip
Auto merge of #12253 - Veykril:bm, r=Veykril
feat: Add binding mode inlay hints

![image](https://user-images.githubusercontent.com/3757771/168427387-2f299438-a0cc-496b-a9a5-d689ef6a2b55.png)
-rw-r--r--crates/hir-ty/src/diagnostics/match_check.rs2
-rw-r--r--crates/hir-ty/src/infer.rs4
-rw-r--r--crates/hir-ty/src/infer/pat.rs9
-rw-r--r--crates/hir-ty/src/lib.rs3
-rw-r--r--crates/hir/src/lib.rs6
-rw-r--r--crates/hir/src/semantics.rs24
-rw-r--r--crates/hir/src/source_analyzer.rs41
-rw-r--r--crates/ide/src/inlay_hints.rs114
-rw-r--r--crates/ide/src/static_index.rs1
-rw-r--r--crates/rust-analyzer/src/config.rs3
-rw-r--r--crates/rust-analyzer/src/to_proto.rs35
-rw-r--r--crates/syntax/src/ast/expr_ext.rs2
-rw-r--r--docs/user/generated_config.adoc5
-rw-r--r--editors/code/package.json5
14 files changed, 212 insertions, 42 deletions
diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs
index d21ea32d9e4..e00dfdf2fa7 100644
--- a/crates/hir-ty/src/diagnostics/match_check.rs
+++ b/crates/hir-ty/src/diagnostics/match_check.rs
@@ -105,7 +105,7 @@ impl<'a> PatCtxt<'a> {
         self.infer.pat_adjustments.get(&pat).map(|it| &**it).unwrap_or_default().iter().rev().fold(
             unadjusted_pat,
             |subpattern, ref_ty| Pat {
-                ty: ref_ty.target.clone(),
+                ty: ref_ty.clone(),
                 kind: Box::new(PatKind::Deref { subpattern }),
             },
         )
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 2a11c9d9bf1..a19ff8bf601 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -297,7 +297,7 @@ pub struct InferenceResult {
     /// Interned Unknown to return references to.
     standard_types: InternedStandardTypes,
     /// Stores the types which were implicitly dereferenced in pattern binding modes.
-    pub pat_adjustments: FxHashMap<PatId, Vec<Adjustment>>,
+    pub pat_adjustments: FxHashMap<PatId, Vec<Ty>>,
     pub pat_binding_modes: FxHashMap<PatId, BindingMode>,
     pub expr_adjustments: FxHashMap<ExprId, Vec<Adjustment>>,
 }
@@ -445,7 +445,7 @@ impl<'a> InferenceContext<'a> {
             adjustment.target = table.resolve_completely(adjustment.target.clone());
         }
         for adjustment in result.pat_adjustments.values_mut().flatten() {
-            adjustment.target = table.resolve_completely(adjustment.target.clone());
+            *adjustment = table.resolve_completely(adjustment.clone());
         }
         result
     }
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index 600b82ca414..f8131314c69 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -11,9 +11,7 @@ use hir_def::{
 use hir_expand::name::Name;
 
 use crate::{
-    infer::{
-        Adjust, Adjustment, AutoBorrow, BindingMode, Expectation, InferenceContext, TypeMismatch,
-    },
+    infer::{BindingMode, Expectation, InferenceContext, TypeMismatch},
     lower::lower_to_chalk_mutability,
     static_lifetime, ConcreteConst, ConstValue, Interner, Substitution, Ty, TyBuilder, TyExt,
     TyKind,
@@ -105,10 +103,7 @@ impl<'a> InferenceContext<'a> {
         if is_non_ref_pat(&self.body, pat) {
             let mut pat_adjustments = Vec::new();
             while let Some((inner, _lifetime, mutability)) = expected.as_reference() {
-                pat_adjustments.push(Adjustment {
-                    target: expected.clone(),
-                    kind: Adjust::Borrow(AutoBorrow::Ref(mutability)),
-                });
+                pat_adjustments.push(expected.clone());
                 expected = self.resolve_ty_shallow(inner);
                 default_bm = match default_bm {
                     BindingMode::Move => BindingMode::Ref(mutability),
diff --git a/crates/hir-ty/src/lib.rs b/crates/hir-ty/src/lib.rs
index 85d8ccc0772..15bcbda25de 100644
--- a/crates/hir-ty/src/lib.rs
+++ b/crates/hir-ty/src/lib.rs
@@ -47,7 +47,8 @@ pub use autoderef::autoderef;
 pub use builder::{ParamKind, TyBuilder};
 pub use chalk_ext::*;
 pub use infer::{
-    could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, InferenceDiagnostic, InferenceResult,
+    could_coerce, could_unify, Adjust, Adjustment, AutoBorrow, BindingMode, InferenceDiagnostic,
+    InferenceResult,
 };
 pub use interner::Interner;
 pub use lower::{
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 39f88937d6e..4f8fc6bf567 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -3332,6 +3332,12 @@ impl Callable {
     }
 }
 
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum BindingMode {
+    Move,
+    Ref(Mutability),
+}
+
 /// For IDE only
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub enum ScopeDef {
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index d887dae99c1..48661ec4ebe 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -30,9 +30,9 @@ use crate::{
     db::HirDatabase,
     semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
     source_analyzer::{resolve_hir_path, SourceAnalyzer},
-    Access, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource, HirFileId, Impl,
-    InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, Path, ScopeDef,
-    ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
+    Access, BindingMode, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource,
+    HirFileId, Impl, InFile, Label, LifetimeParam, Local, Macro, Module, ModuleDef, Name, Path,
+    ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -336,6 +336,14 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.type_of_self(param)
     }
 
+    pub fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> {
+        self.imp.pattern_adjustments(pat)
+    }
+
+    pub fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> {
+        self.imp.binding_mode_of_pat(pat)
+    }
+
     pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
         self.imp.resolve_method_call(call).map(Function::from)
     }
@@ -951,6 +959,16 @@ impl<'db> SemanticsImpl<'db> {
         self.analyze(param.syntax())?.type_of_self(self.db, param)
     }
 
+    fn pattern_adjustments(&self, pat: &ast::Pat) -> SmallVec<[Type; 1]> {
+        self.analyze(pat.syntax())
+            .and_then(|it| it.pattern_adjustments(self.db, pat))
+            .unwrap_or_default()
+    }
+
+    fn binding_mode_of_pat(&self, pat: &ast::IdentPat) -> Option<BindingMode> {
+        self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat)
+    }
+
     fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
         self.analyze(call.syntax())?.resolve_method_call(self.db, call).map(|(id, _)| id)
     }
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 7a65fd99cfa..b5e6d99093d 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -34,15 +34,16 @@ use hir_ty::{
     Adjust, Adjustment, AutoBorrow, InferenceResult, Interner, Substitution, TyExt,
     TyLoweringContext,
 };
+use smallvec::SmallVec;
 use syntax::{
     ast::{self, AstNode},
     SyntaxKind, SyntaxNode, TextRange, TextSize,
 };
 
 use crate::{
-    db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BuiltinAttr, BuiltinType, Const,
-    Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule, Trait, Type, TypeAlias,
-    Variant,
+    db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
+    BuiltinType, Const, Field, Function, Local, Macro, ModuleDef, Static, Struct, ToolModule,
+    Trait, Type, TypeAlias, Variant,
 };
 
 /// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
@@ -182,7 +183,7 @@ impl SourceAnalyzer {
         let coerced = infer
             .pat_adjustments
             .get(&pat_id)
-            .and_then(|adjusts| adjusts.last().map(|adjust| adjust.target.clone()));
+            .and_then(|adjusts| adjusts.last().map(|adjust| adjust.clone()));
         let ty = infer[pat_id].clone();
         let mk_ty = |ty| Type::new_with_resolver(db, &self.resolver, ty);
         Some((mk_ty(ty), coerced.map(mk_ty)))
@@ -199,6 +200,38 @@ impl SourceAnalyzer {
         Some(Type::new_with_resolver(db, &self.resolver, ty))
     }
 
+    pub(crate) fn binding_mode_of_pat(
+        &self,
+        _db: &dyn HirDatabase,
+        pat: &ast::IdentPat,
+    ) -> Option<BindingMode> {
+        let pat_id = self.pat_id(&pat.clone().into())?;
+        let infer = self.infer.as_ref()?;
+        infer.pat_binding_modes.get(&pat_id).map(|bm| match bm {
+            hir_ty::BindingMode::Move => BindingMode::Move,
+            hir_ty::BindingMode::Ref(hir_ty::Mutability::Mut) => BindingMode::Ref(Mutability::Mut),
+            hir_ty::BindingMode::Ref(hir_ty::Mutability::Not) => {
+                BindingMode::Ref(Mutability::Shared)
+            }
+        })
+    }
+    pub(crate) fn pattern_adjustments(
+        &self,
+        db: &dyn HirDatabase,
+        pat: &ast::Pat,
+    ) -> Option<SmallVec<[Type; 1]>> {
+        let pat_id = self.pat_id(&pat)?;
+        let infer = self.infer.as_ref()?;
+        Some(
+            infer
+                .pat_adjustments
+                .get(&pat_id)?
+                .iter()
+                .map(|ty| Type::new_with_resolver(db, &self.resolver, ty.clone()))
+                .collect(),
+        )
+    }
+
     pub(crate) fn resolve_method_call(
         &self,
         db: &dyn HirDatabase,
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index a7cb1a599f5..23a46c02762 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -1,5 +1,5 @@
 use either::Either;
-use hir::{known, Callable, HasVisibility, HirDisplay, Semantics, TypeInfo};
+use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo};
 use ide_db::{
     base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
     RootDatabase,
@@ -21,6 +21,7 @@ pub struct InlayHintsConfig {
     pub chaining_hints: bool,
     pub reborrow_hints: ReborrowHints,
     pub closure_return_type_hints: bool,
+    pub binding_mode_hints: bool,
     pub lifetime_elision_hints: LifetimeElisionHints,
     pub param_names_for_lifetime_elision_hints: bool,
     pub hide_named_constructor_hints: bool,
@@ -43,10 +44,11 @@ pub enum ReborrowHints {
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum InlayKind {
+    BindingModeHint,
     ChainingHint,
     ClosureReturnTypeHint,
     GenericParamListHint,
-    ImplicitReborrow,
+    ImplicitReborrowHint,
     LifetimeHint,
     ParameterHint,
     TypeHint,
@@ -135,8 +137,11 @@ fn hints(
             ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
             _ => None,
         };
-    } else if let Some(it) = ast::IdentPat::cast(node.clone()) {
-        bind_pat_hints(hints, sema, config, &it);
+    } else if let Some(it) = ast::Pat::cast(node.clone()) {
+        binding_mode_hints(hints, sema, config, &it);
+        if let ast::Pat::IdentPat(it) = it {
+            bind_pat_hints(hints, sema, config, &it);
+        }
     } else if let Some(it) = ast::Fn::cast(node) {
         lifetime_hints(hints, config, it);
     }
@@ -383,7 +388,9 @@ fn reborrow_hints(
         return None;
     }
 
-    let mutability = sema.is_implicit_reborrow(expr)?;
+    let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+    let desc_expr = descended.as_ref().unwrap_or(expr);
+    let mutability = sema.is_implicit_reborrow(desc_expr)?;
     let label = match mutability {
         hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
         hir::Mutability::Mut => "&mut *",
@@ -391,7 +398,7 @@ fn reborrow_hints(
     };
     acc.push(InlayHint {
         range: expr.syntax().text_range(),
-        kind: InlayKind::ImplicitReborrow,
+        kind: InlayKind::ImplicitReborrowHint,
         label: SmolStr::new_inline(label),
     });
     Some(())
@@ -497,6 +504,51 @@ fn param_name_hints(
     Some(())
 }
 
+fn binding_mode_hints(
+    acc: &mut Vec<InlayHint>,
+    sema: &Semantics<RootDatabase>,
+    config: &InlayHintsConfig,
+    pat: &ast::Pat,
+) -> Option<()> {
+    if !config.binding_mode_hints {
+        return None;
+    }
+
+    let range = pat.syntax().text_range();
+    sema.pattern_adjustments(&pat).iter().for_each(|ty| {
+        let reference = ty.is_reference();
+        let mut_reference = ty.is_mutable_reference();
+        let r = match (reference, mut_reference) {
+            (true, true) => "&mut",
+            (true, false) => "&",
+            _ => return,
+        };
+        acc.push(InlayHint {
+            range,
+            kind: InlayKind::BindingModeHint,
+            label: SmolStr::new_inline(r),
+        });
+    });
+    match pat {
+        ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
+            let bm = sema.binding_mode_of_pat(pat)?;
+            let bm = match bm {
+                hir::BindingMode::Move => return None,
+                hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
+                hir::BindingMode::Ref(Mutability::Shared) => "ref",
+            };
+            acc.push(InlayHint {
+                range,
+                kind: InlayKind::BindingModeHint,
+                label: SmolStr::new_inline(bm),
+            });
+        }
+        _ => (),
+    }
+
+    Some(())
+}
+
 fn bind_pat_hints(
     acc: &mut Vec<InlayHint>,
     sema: &Semantics<RootDatabase>,
@@ -681,6 +733,7 @@ fn should_not_display_type_hint(
         match_ast! {
             match node {
                 ast::LetStmt(it) => return it.ty().is_some(),
+                // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
                 ast::Param(it) => return it.ty().is_some(),
                 ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
                 ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
@@ -866,9 +919,10 @@ mod tests {
         parameter_hints: false,
         chaining_hints: false,
         lifetime_elision_hints: LifetimeElisionHints::Never,
-        hide_named_constructor_hints: false,
         closure_return_type_hints: false,
         reborrow_hints: ReborrowHints::Always,
+        binding_mode_hints: false,
+        hide_named_constructor_hints: false,
         param_names_for_lifetime_elision_hints: false,
         max_length: None,
     };
@@ -878,6 +932,7 @@ mod tests {
         chaining_hints: true,
         reborrow_hints: ReborrowHints::Always,
         closure_return_type_hints: true,
+        binding_mode_hints: true,
         lifetime_elision_hints: LifetimeElisionHints::Always,
         ..DISABLED_CONFIG
     };
@@ -2194,4 +2249,49 @@ fn ref_id(shared_ref: &()) -> &() {
 "#,
         );
     }
+
+    #[test]
+    fn hints_binding_modes() {
+        check_with_config(
+            InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
+            r#"
+fn __(
+    (x,): (u32,),
+    (x,): &(u32,),
+  //^^^^&
+   //^ ref
+    (x,): &mut (u32,)
+  //^^^^&mut
+   //^ ref mut
+) {
+    let (x,) = (0,);
+    let (x,) = &(0,);
+      //^^^^ &
+       //^ ref
+    let (x,) = &mut (0,);
+      //^^^^ &mut
+       //^ ref mut
+    let &mut (x,) = &mut (0,);
+    let (ref mut x,) = &mut (0,);
+      //^^^^^^^^^^^^ &mut
+    let &mut (ref mut x,) = &mut (0,);
+    let (mut x,) = &mut (0,);
+      //^^^^^^^^ &mut
+    match (0,) {
+        (x,) => ()
+    }
+    match &(0,) {
+        (x,) => ()
+      //^^^^ &
+       //^ ref
+    }
+    match &mut (0,) {
+        (x,) => ()
+      //^^^^ &mut
+       //^ ref mut
+    }
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 48159f5958d..53820860f5d 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -114,6 +114,7 @@ impl StaticIndex<'_> {
                     reborrow_hints: crate::ReborrowHints::Never,
                     hide_named_constructor_hints: false,
                     param_names_for_lifetime_elision_hints: false,
+                    binding_mode_hints: false,
                     max_length: Some(25),
                 },
                 file_id,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 3e5561ad389..623f5ec9437 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -255,6 +255,8 @@ config_data! {
         /// The path structure for newly inserted paths to use.
         imports_prefix: ImportPrefixDef               = "\"plain\"",
 
+        /// Whether to show inlay type hints for binding modes.
+        inlayHints_bindingModeHints_enable: bool                   = "false",
         /// Whether to show inlay type hints for method chains.
         inlayHints_chainingHints_enable: bool                      = "true",
         /// Whether to show inlay type hints for return types of closures with blocks.
@@ -998,6 +1000,7 @@ impl Config {
                 ReborrowHintsDef::Never => ide::ReborrowHints::Never,
                 ReborrowHintsDef::Mutable => ide::ReborrowHints::MutableOnly,
             },
+            binding_mode_hints: self.data.inlayHints_bindingModeHints_enable,
             param_names_for_lifetime_elision_hints: self
                 .data
                 .inlayHints_lifetimeElisionHints_useParameterNames,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index ff348959add..cd9226d03da 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -418,9 +418,9 @@ pub(crate) fn inlay_hint(
     lsp_types::InlayHint {
         position: match inlay_hint.kind {
             // before annotated thing
-            InlayKind::ParameterHint | InlayKind::ImplicitReborrow => {
-                position(line_index, inlay_hint.range.start())
-            }
+            InlayKind::ParameterHint
+            | InlayKind::ImplicitReborrowHint
+            | InlayKind::BindingModeHint => position(line_index, inlay_hint.range.start()),
             // after annotated thing
             InlayKind::ClosureReturnTypeHint
             | InlayKind::TypeHint
@@ -439,27 +439,30 @@ pub(crate) fn inlay_hint(
             InlayKind::ClosureReturnTypeHint | InlayKind::TypeHint | InlayKind::ChainingHint => {
                 Some(lsp_types::InlayHintKind::TYPE)
             }
-            InlayKind::GenericParamListHint
+            InlayKind::BindingModeHint
+            | InlayKind::GenericParamListHint
             | InlayKind::LifetimeHint
-            | InlayKind::ImplicitReborrow => None,
+            | InlayKind::ImplicitReborrowHint => None,
         },
         tooltip: None,
         padding_left: Some(match inlay_hint.kind {
             InlayKind::TypeHint => !render_colons,
-            InlayKind::ParameterHint | InlayKind::ClosureReturnTypeHint => false,
             InlayKind::ChainingHint => true,
-            InlayKind::GenericParamListHint => false,
-            InlayKind::LifetimeHint => false,
-            InlayKind::ImplicitReborrow => false,
+            InlayKind::BindingModeHint
+            | InlayKind::ClosureReturnTypeHint
+            | InlayKind::GenericParamListHint
+            | InlayKind::ImplicitReborrowHint
+            | InlayKind::LifetimeHint
+            | InlayKind::ParameterHint => false,
         }),
         padding_right: Some(match inlay_hint.kind {
-            InlayKind::TypeHint | InlayKind::ChainingHint | InlayKind::ClosureReturnTypeHint => {
-                false
-            }
-            InlayKind::ParameterHint => true,
-            InlayKind::LifetimeHint => true,
-            InlayKind::GenericParamListHint => false,
-            InlayKind::ImplicitReborrow => false,
+            InlayKind::ChainingHint
+            | InlayKind::ClosureReturnTypeHint
+            | InlayKind::GenericParamListHint
+            | InlayKind::ImplicitReborrowHint
+            | InlayKind::TypeHint => false,
+            InlayKind::BindingModeHint => inlay_hint.label != "&",
+            InlayKind::ParameterHint | InlayKind::LifetimeHint => true,
         }),
         text_edits: None,
         data: None,
diff --git a/crates/syntax/src/ast/expr_ext.rs b/crates/syntax/src/ast/expr_ext.rs
index 17785152bc5..db66d08a73b 100644
--- a/crates/syntax/src/ast/expr_ext.rs
+++ b/crates/syntax/src/ast/expr_ext.rs
@@ -273,7 +273,7 @@ impl ast::ArrayExpr {
     }
 
     fn is_repeat(&self) -> bool {
-        self.syntax().children_with_tokens().any(|it| it.kind() == T![;])
+        self.semicolon_token().is_some()
     }
 }
 
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index f2c8cb95c9b..57f04067a3d 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -345,6 +345,11 @@ Whether to allow import insertion to merge new imports into single path glob imp
 --
 The path structure for newly inserted paths to use.
 --
+[[rust-analyzer.inlayHints.bindingModeHints.enable]]rust-analyzer.inlayHints.bindingModeHints.enable (default: `false`)::
++
+--
+Whether to show inlay type hints for binding modes.
+--
 [[rust-analyzer.inlayHints.chainingHints.enable]]rust-analyzer.inlayHints.chainingHints.enable (default: `true`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index a69d224659f..f34f9354e62 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -782,6 +782,11 @@
                         "Force import paths to be absolute by always starting them with `crate` or the extern crate name they come from."
                     ]
                 },
+                "rust-analyzer.inlayHints.bindingModeHints.enable": {
+                    "markdownDescription": "Whether to show inlay type hints for binding modes.",
+                    "default": false,
+                    "type": "boolean"
+                },
                 "rust-analyzer.inlayHints.chainingHints.enable": {
                     "markdownDescription": "Whether to show inlay type hints for method chains.",
                     "default": true,