Repository: rust-lang/rust-analyzer
Pull request #16679 (2024-02-26 12:51:39)

fix: Fix modules in blocks not resolving in ide layer
Fixes https://github.com/rust-lang/rust-analyzer/issues/16511


Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 97c1b64494..5faee21bdb 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -23,11 +23,3 @@ Otherwise please try to provide information which will help us to fix the issue
 **rustc version**: (eg. output of `rustc -V`)
 
 **relevant settings**: (eg. client settings, or environment variables like `CARGO`, `RUSTC`, `RUSTUP_HOME` or `CARGO_HOME`)
-
-**repository link (if public, optional)**: (eg. [rust-analyzer](https://github.com/rust-lang/rust-analyzer))
-
-**code snippet to reproduce**: 
-```rust
-// add your code here
-
-```
diff --git a/.typos.toml b/.typos.toml
index 98dbe3a5d9..e638a3e648 100644
--- a/.typos.toml
+++ b/.typos.toml
@@ -1,21 +1,8 @@
-[files]
-extend-exclude = [
-    "*.rast",
-    "bench_data/",
-    "crates/parser/test_data/lexer/err/",
-    "crates/project-model/test_data/",
-]
-ignore-hidden = false
-
-[default]
-extend-ignore-re = [
-    # ignore string which contains $0, which is used widely in tests
-    ".*\\$0.*",
-    # ignore generated content like `boxed....nner()`, `Defaul...efault`
-    "\\w*\\.{3,4}\\w*",
-    '"flate2"',
-    "raison d'être",
-]
+[default.extend-identifiers]
+AnserStyle = "AnserStyle"
+datas = "datas"
+impl_froms = "impl_froms"
+selfs = "selfs"
 
 [default.extend-words]
 anser = "anser"
@@ -23,9 +10,22 @@ ba = "ba"
 fo = "fo"
 ket = "ket"
 makro = "makro"
+raison = "raison"
 trivias = "trivias"
+TOOD = "TOOD"
 
-[default.extend-identifiers]
-datas = "datas"
-impl_froms = "impl_froms"
-selfs = "selfs"
+[default]
+extend-ignore-re = [
+    # ignore string which contains $x (x is a num), which use widely in test
+    ".*\\$\\d.*",
+    # ignore generated content like `boxed....nner()`, `Defaul...efault`
+    "\\w*\\.{3,4}\\w*",
+]
+
+[files]
+extend-exclude = [
+  "*.json",
+  "*.rast",
+  "crates/parser/test_data/lexer/err/*",
+  "bench_data/*",
+]
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index a817cd0c3a..b243b37b77 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -570,7 +570,7 @@ impl CrateGraph {
                 .arena
                 .iter_mut()
                 .take(m)
-                .find_map(|(id, data)| merge((id, data), (topo, &crate_data)).then_some(id));
+                .find_map(|(id, data)| merge((id, data), (topo, crate_data)).then_some(id));
 
             let new_id =
                 if let Some(res) = res { res } else { self.arena.alloc(crate_data.clone()) };
diff --git a/crates/hir-def/src/child_by_source.rs b/crates/hir-def/src/child_by_source.rs
index ba7d06272a..f1c6b3b89f 100644
--- a/crates/hir-def/src/child_by_source.rs
+++ b/crates/hir-def/src/child_by_source.rs
@@ -189,10 +189,11 @@ impl ChildBySource for DefWithBodyId {
             VariantId::EnumVariantId(v).child_by_source_to(db, res, file_id)
         }
 
-        for (_, def_map) in body.blocks(db) {
+        for (block, def_map) in body.blocks(db) {
             // All block expressions are merged into the same map, because they logically all add
             // inner items to the containing `DefWithBodyId`.
             def_map[DefMap::ROOT].scope.child_by_source_to(db, res, file_id);
+            res[keys::BLOCK].insert(block.lookup(db).ast_id.to_node(db.upcast()), block);
         }
     }
 }
diff --git a/crates/hir-def/src/dyn_map/keys.rs b/crates/hir-def/src/dyn_map/keys.rs
index 60832f59eb..f83ab1e1a0 100644
--- a/crates/hir-def/src/dyn_map/keys.rs
+++ b/crates/hir-def/src/dyn_map/keys.rs
@@ -8,13 +8,14 @@ use syntax::{ast, AstNode, AstPtr};
 
 use crate::{
     dyn_map::{DynMap, Policy},
-    ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId, LifetimeParamId,
-    Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId, TraitId, TypeAliasId,
-    TypeOrConstParamId, UnionId, UseId,
+    BlockId, ConstId, EnumId, EnumVariantId, ExternCrateId, FieldId, FunctionId, ImplId,
+    LifetimeParamId, Macro2Id, MacroRulesId, ProcMacroId, StaticId, StructId, TraitAliasId,
+    TraitId, TypeAliasId, TypeOrConstParamId, UnionId, UseId,
 };
 
 pub type Key<K, V> = crate::dyn_map::Key<K, V, AstPtrPolicy<K, V>>;
 
+pub const BLOCK: Key<ast::BlockExpr, BlockId> = Key::new();
 pub const FUNCTION: Key<ast::Fn, FunctionId> = Key::new();
 pub const CONST: Key<ast::Const, ConstId> = Key::new();
 pub const STATIC: Key<ast::Static, StaticId> = Key::new();
diff --git a/crates/hir-def/src/nameres.rs b/crates/hir-def/src/nameres.rs
index a2eca06643..1a82782115 100644
--- a/crates/hir-def/src/nameres.rs
+++ b/crates/hir-def/src/nameres.rs
@@ -469,6 +469,12 @@ impl DefMap {
         CrateRootModuleId { krate: self.krate }
     }
 
+    /// This is the same as [`Self::crate_root`] for crate def maps, but for block def maps, it
+    /// returns the root block module.
+    pub fn root_module_id(&self) -> ModuleId {
+        self.module_id(Self::ROOT)
+    }
+
     pub(crate) fn resolve_path(
         &self,
         db: &dyn DefDatabase,
diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs
index db47d743c5..226d6f513f 100644
--- a/crates/hir-def/src/resolver.rs
+++ b/crates/hir-def/src/resolver.rs
@@ -1,5 +1,5 @@
 //! Name resolution façade.
-use std::{fmt, hash::BuildHasherDefault};
+use std::{fmt, hash::BuildHasherDefault, mem};
 
 use base_db::CrateId;
 use hir_expand::{
@@ -809,7 +809,7 @@ fn resolver_for_scope_(
     for scope in scope_chain.into_iter().rev() {
         if let Some(block) = scopes.block(scope) {
             let def_map = db.block_def_map(block);
-            r = r.push_block_scope(def_map, DefMap::ROOT);
+            r = r.push_block_scope(def_map);
             // FIXME: This adds as many module scopes as there are blocks, but resolving in each
             // already traverses all parents, so this is O(n²). I think we could only store the
             // innermost module scope instead?
@@ -835,8 +835,9 @@ impl Resolver {
         self.push_scope(Scope::ImplDefScope(impl_def))
     }
 
-    fn push_block_scope(self, def_map: Arc<DefMap>, module_id: LocalModuleId) -> Resolver {
-        self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, module_id }))
+    fn push_block_scope(self, def_map: Arc<DefMap>) -> Resolver {
+        debug_assert!(def_map.block_id().is_some());
+        self.push_scope(Scope::BlockScope(ModuleItemMap { def_map, module_id: DefMap::ROOT }))
     }
 
     fn push_expr_scope(
@@ -986,19 +987,27 @@ pub trait HasResolver: Copy {
 impl HasResolver for ModuleId {
     fn resolver(self, db: &dyn DefDatabase) -> Resolver {
         let mut def_map = self.def_map(db);
-        let mut modules: SmallVec<[_; 1]> = smallvec![];
         let mut module_id = self.local_id;
+        let mut modules: SmallVec<[_; 1]> = smallvec![];
+
+        if !self.is_block_module() {
+            return Resolver { scopes: vec![], module_scope: ModuleItemMap { def_map, module_id } };
+        }
+
         while let Some(parent) = def_map.parent() {
-            modules.push((def_map, module_id));
-            def_map = parent.def_map(db);
-            module_id = parent.local_id;
+            let block_def_map = mem::replace(&mut def_map, parent.def_map(db));
+            modules.push(block_def_map);
+            if !parent.is_block_module() {
+                module_id = parent.local_id;
+                break;
+            }
         }
         let mut resolver = Resolver {
             scopes: Vec::with_capacity(modules.len()),
             module_scope: ModuleItemMap { def_map, module_id },
         };
-        for (def_map, module) in modules.into_iter().rev() {
-            resolver = resolver.push_block_scope(def_map, module);
+        for def_map in modules.into_iter().rev() {
+            resolver = resolver.push_block_scope(def_map);
         }
         resolver
     }
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 1a134e6d78..6c8a187516 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -17,7 +17,6 @@ use tracing::debug;
 use triomphe::Arc;
 use typed_arena::Arena;
 
-use crate::Interner;
 use crate::{
     db::HirDatabase,
     diagnostics::match_check::{
@@ -150,18 +149,17 @@ impl ExprValidator {
                 None => return,
             };
 
-            let checker = filter_map_next_checker.get_or_insert_with(|| {
-                FilterMapNextChecker::new(&self.owner.resolver(db.upcast()), db)
-            });
-
-            if checker.check(call_id, receiver, &callee).is_some() {
+            if filter_map_next_checker
+                .get_or_insert_with(|| {
+                    FilterMapNextChecker::new(&self.owner.resolver(db.upcast()), db)
+                })
+                .check(call_id, receiver, &callee)
+                .is_some()
+            {
                 self.diagnostics.push(BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
                     method_call_expr: call_id,
                 });
             }
-
-            let receiver_ty = self.infer[*receiver].clone();
-            checker.prev_receiver_ty = Some(receiver_ty);
         }
     }
 
@@ -395,7 +393,6 @@ struct FilterMapNextChecker {
     filter_map_function_id: Option<hir_def::FunctionId>,
     next_function_id: Option<hir_def::FunctionId>,
     prev_filter_map_expr_id: Option<ExprId>,
-    prev_receiver_ty: Option<chalk_ir::Ty<Interner>>,
 }
 
 impl FilterMapNextChecker {
@@ -420,12 +417,7 @@ impl FilterMapNextChecker {
             ),
             None => (None, None),
         };
-        Self {
-            filter_map_function_id,
-            next_function_id,
-            prev_filter_map_expr_id: None,
-            prev_receiver_ty: None,
-        }
+        Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None }
     }
 
     // check for instances of .filter_map(..).next()
@@ -442,11 +434,7 @@ impl FilterMapNextChecker {
 
         if *function_id == self.next_function_id? {
             if let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id {
-                let is_dyn_trait = self
-                    .prev_receiver_ty
-                    .as_ref()
-                    .map_or(false, |it| it.strip_references().dyn_trait().is_some());
-                if *receiver_expr_id == prev_filter_map_expr_id && !is_dyn_trait {
+                if *receiver_expr_id == prev_filter_map_expr_id {
                     return Some(());
                 }
             }
diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs
index 000248c2ce..22a70f951e 100644
--- a/crates/hir-ty/src/infer/closure.rs
+++ b/crates/hir-ty/src/infer/closure.rs
@@ -23,7 +23,7 @@ use stdx::never;
 use crate::{
     db::{HirDatabase, InternedClosure},
     from_placeholder_idx, make_binders,
-    mir::{BorrowKind, MirSpan, MutBorrowKind, ProjectionElem},
+    mir::{BorrowKind, MirSpan, ProjectionElem},
     static_lifetime, to_chalk_trait_id,
     traits::FnTrait,
     utils::{self, generics, Generics},
@@ -142,13 +142,9 @@ impl HirPlace {
         mut current_capture: CaptureKind,
         len: usize,
     ) -> CaptureKind {
-        if let CaptureKind::ByRef(BorrowKind::Mut {
-            kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
-        }) = current_capture
-        {
+        if let CaptureKind::ByRef(BorrowKind::Mut { .. }) = current_capture {
             if self.projections[len..].iter().any(|it| *it == ProjectionElem::Deref) {
-                current_capture =
-                    CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture });
+                current_capture = CaptureKind::ByRef(BorrowKind::Unique);
             }
         }
         current_capture
@@ -381,7 +377,7 @@ impl InferenceContext<'_> {
         if let Some(place) = self.place_of_expr(expr) {
             self.add_capture(
                 place,
-                CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default }),
+                CaptureKind::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false }),
                 expr.into(),
             );
         }
@@ -430,7 +426,9 @@ impl InferenceContext<'_> {
 
     fn ref_capture_with_adjusts(&mut self, m: Mutability, tgt_expr: ExprId, rest: &[Adjustment]) {
         let capture_kind = match m {
-            Mutability::Mut => CaptureKind::ByRef(BorrowKind::Mut { kind: MutBorrowKind::Default }),
+            Mutability::Mut => {
+                CaptureKind::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false })
+            }
             Mutability::Not => CaptureKind::ByRef(BorrowKind::Shared),
         };
         if let Some(place) = self.place_of_expr_without_adjust(tgt_expr) {
@@ -650,7 +648,7 @@ impl InferenceContext<'_> {
         self.walk_pat_inner(
             pat,
             &mut update_result,
-            BorrowKind::Mut { kind: MutBorrowKind::Default },
+            BorrowKind::Mut { allow_two_phase_borrow: false },
         );
     }
 
@@ -701,7 +699,7 @@ impl InferenceContext<'_> {
             },
         }
         if self.result.pat_adjustments.get(&p).map_or(false, |it| !it.is_empty()) {
-            for_mut = BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture };
+            for_mut = BorrowKind::Unique;
         }
         self.body.walk_pats_shallow(p, |p| self.walk_pat_inner(p, update_result, for_mut));
     }
@@ -882,7 +880,7 @@ impl InferenceContext<'_> {
                     }
                     BindingMode::Ref(Mutability::Not) => BorrowKind::Shared,
                     BindingMode::Ref(Mutability::Mut) => {
-                        BorrowKind::Mut { kind: MutBorrowKind::Default }
+                        BorrowKind::Mut { allow_two_phase_borrow: false }
                     }
                 };
                 self.add_capture(place, CaptureKind::ByRef(capture_kind), pat.into());
@@ -932,7 +930,9 @@ impl InferenceContext<'_> {
             r = cmp::min(
                 r,
                 match &it.kind {
-                    CaptureKind::ByRef(BorrowKind::Mut { .. }) => FnTrait::FnMut,
+                    CaptureKind::ByRef(BorrowKind::Unique | BorrowKind::Mut { .. }) => {
+                        FnTrait::FnMut
+                    }
                     CaptureKind::ByRef(BorrowKind::Shallow | BorrowKind::Shared) => FnTrait::Fn,
                     CaptureKind::ByValue => FnTrait::FnOnce,
                 },
@@ -949,12 +949,8 @@ impl InferenceContext<'_> {
         };
         self.consume_expr(*body);
         for item in &self.current_captures {
-            if matches!(
-                item.kind,
-                CaptureKind::ByRef(BorrowKind::Mut {
-                    kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow
-                })
-            ) && !item.place.projections.contains(&ProjectionElem::Deref)
+            if matches!(item.kind, CaptureKind::ByRef(BorrowKind::Mut { .. }))
+                && !item.place.projections.contains(&ProjectionElem::Deref)
             {
                 // FIXME: remove the `mutated_bindings_in_closure` completely and add proper fake reads in
                 // MIR. I didn't do that due duplicate diagnostics.
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index cfaef2a392..494f1850b8 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -659,33 +659,66 @@ pub enum BorrowKind {
     /// We can also report errors with this kind of borrow differently.
     Shallow,
 
-    /// Data is mutable and not aliasable.
-    Mut { kind: MutBorrowKind },
-}
+    /// Data must be immutable but not aliasable. This kind of borrow
+    /// cannot currently be expressed by the user and is used only in
+    /// implicit closure bindings. It is needed when the closure is
+    /// borrowing or mutating a mutable referent, e.g.:
+    /// ```
+    /// let mut z = 3;
+    /// let x: &mut isize = &mut z;
+    /// let y = || *x += 5;
+    /// ```
+    /// If we were to try to translate this closure into a more explicit
+    /// form, we'd encounter an error with the code as written:
+    /// ```compile_fail,E0594
+    /// struct Env<'a> { x: &'a &'a mut isize }
+    /// let mut z = 3;
+    /// let x: &mut isize = &mut z;
+    /// let y = (&mut Env { x: &x }, fn_ptr);  // Closure is pair of env and fn
+    /// fn fn_ptr(env: &mut Env) { **env.x += 5; }
+    /// ```
+    /// This is then illegal because you cannot mutate an `&mut` found
+    /// in an aliasable location. To solve, you'd have to translate with
+    /// an `&mut` borrow:
+    /// ```compile_fail,E0596
+    /// struct Env<'a> { x: &'a mut &'a mut isize }
+    /// let mut z = 3;
+    /// let x: &mut isize = &mut z;
+    /// let y = (&mut Env { x: &mut x }, fn_ptr); // changed from &x to &mut x
+    /// fn fn_ptr(env: &mut Env) { **env.x += 5; }
+    /// ```
+    /// Now the assignment to `**env.x` is legal, but creating a
+    /// mutable pointer to `x` is not because `x` is not mutable. We
+    /// could fix this by declaring `x` as `let mut x`. This is ok in
+    /// user code, if awkward, but extra weird for closures, since the
+    /// borrow is hidden.
+    ///
+    /// So we introduce a "unique imm" borrow -- the referent is
+    /// immutable, but not aliasable. This solves the problem. For
+    /// simplicity, we don't give users the way to express this
+    /// borrow, it's just used when translating closures.
+    Unique,
 
-#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
-pub enum MutBorrowKind {
-    Default,
-    /// This borrow arose from method-call auto-ref
-    /// (i.e., adjustment::Adjust::Borrow).
-    TwoPhasedBorrow,
-    /// Data must be immutable but not aliasable. This kind of borrow cannot currently
-    /// be expressed by the user and is used only in implicit closure bindings.
-    ClosureCapture,
+    /// Data is mutable and not aliasable.
+    Mut {
+        /// `true` if this borrow arose from method-call auto-ref
+        /// (i.e., `adjustment::Adjust::Borrow`).
+        allow_two_phase_borrow: bool,
+    },
 }
 
 impl BorrowKind {
     fn from_hir(m: hir_def::type_ref::Mutability) -> Self {
         match m {
             hir_def::type_ref::Mutability::Shared => BorrowKind::Shared,
-            hir_def::type_ref::Mutability::Mut => BorrowKind::Mut { kind: MutBorrowKind::Default },
+            hir_def::type_ref::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
         }
     }
 
     fn from_chalk(m: Mutability) -> Self {
         match m {
             Mutability::Not => BorrowKind::Shared,
-            Mutability::Mut => BorrowKind::Mut { kind: MutBorrowKind::Default },
+            Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false },
         }
     }
 }
diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs
index 8b6936f8bc..63fa87ad66 100644
--- a/crates/hir-ty/src/mir/borrowck.rs
+++ b/crates/hir-ty/src/mir/borrowck.rs
@@ -19,8 +19,8 @@ use crate::{
 };
 
 use super::{
-    BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, MutBorrowKind, Place,
-    ProjectionElem, Rvalue, StatementKind, TerminatorKind,
+    BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem,
+    Rvalue, StatementKind, TerminatorKind,
 };
 
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -540,13 +540,7 @@ fn mutability_of_locals(
                         }
                         Rvalue::ShallowInitBox(_, _) | Rvalue::ShallowInitBoxWithAlloc(_) => (),
                     }
-                    if let Rvalue::Ref(
-                        BorrowKind::Mut {
-                            kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
-                        },
-                        p,
-                    ) = value
-                    {
+                    if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
                         if place_case(db, body, p) != ProjectionCase::Indirect {
                             push_mut_span(p.local, statement.span, &mut result);
                         }
diff --git a/crates/hir-ty/src/mir/lower/as_place.rs b/crates/hir-ty/src/mir/lower/as_place.rs
index be81915bb4..afe33607d4 100644
--- a/crates/hir-ty/src/mir/lower/as_place.rs
+++ b/crates/hir-ty/src/mir/lower/as_place.rs
@@ -1,7 +1,5 @@
 //! MIR lowering for places
 
-use crate::mir::MutBorrowKind;
-
 use super::*;
 use hir_def::FunctionId;
 use hir_expand::name;
@@ -330,7 +328,7 @@ impl MirLowerCtx<'_> {
                 Mutability::Mut,
                 LangItem::DerefMut,
                 name![deref_mut],
-                BorrowKind::Mut { kind: MutBorrowKind::Default },
+                BorrowKind::Mut { allow_two_phase_borrow: false },
             )
         };
         let ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), source_ty.clone()).intern(Interner);
diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs
index 90cbd13a6c..85c8d1685b 100644
--- a/crates/hir-ty/src/mir/lower/pattern_matching.rs
+++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs
@@ -3,15 +3,12 @@
 use hir_def::{hir::LiteralOrConst, resolver::HasResolver, AssocItemId};
 
 use crate::{
-    mir::{
-        lower::{
-            BasicBlockId, BinOp, BindingId, BorrowKind, Either, Expr, FieldId, Idx, Interner,
-            MemoryMap, MirLowerCtx, MirLowerError, MirSpan, Mutability, Operand, Pat, PatId, Place,
-            PlaceElem, ProjectionElem, RecordFieldPat, ResolveValueResult, Result, Rvalue,
-            Substitution, SwitchTargets, TerminatorKind, TupleFieldId, TupleId, TyBuilder, TyKind,
-            ValueNs, VariantData, VariantId,
-        },
-        MutBorrowKind,
+    mir::lower::{
+        BasicBlockId, BinOp, BindingId, BorrowKind, Either, Expr, FieldId, Idx, Interner,
+        MemoryMap, MirLowerCtx, MirLowerError, MirSpan, Mutability, Operand, Pat, PatId, Place,
+        PlaceElem, ProjectionElem, RecordFieldPat, ResolveValueResult, Result, Rvalue,
+        Substitution, SwitchTargets, TerminatorKind, TupleFieldId, TupleId, TyBuilder, TyKind,
+        ValueNs, VariantData, VariantId,
     },
     BindingMode,
 };
@@ -453,7 +450,7 @@ impl MirLowerCtx<'_> {
                 BindingMode::Move => Operand::Copy(cond_place).into(),
                 BindingMode::Ref(Mutability::Not) => Rvalue::Ref(BorrowKind::Shared, cond_place),
                 BindingMode::Ref(Mutability::Mut) => {
-                    Rvalue::Ref(BorrowKind::Mut { kind: MutBorrowKind::Default }, cond_place)
+                    Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, cond_place)
                 }
             },
             span,
diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs
index 0c641d7c6c..23fc271355 100644
--- a/crates/hir-ty/src/mir/pretty.rs
+++ b/crates/hir-ty/src/mir/pretty.rs
@@ -18,8 +18,7 @@ use crate::{
 };
 
 use super::{
-    AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, MutBorrowKind, Operand, Place,
-    Rvalue, UnOp,
+    AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp,
 };
 
 macro_rules! w {
@@ -367,10 +366,8 @@ impl<'a> MirPrettyCtx<'a> {
                 match r {
                     BorrowKind::Shared => w!(self, "&"),
                     BorrowKind::Shallow => w!(self, "&shallow "),
-                    BorrowKind::Mut { kind: MutBorrowKind::ClosureCapture } => w!(self, "&uniq "),
-                    BorrowKind::Mut {
-                        kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
-                    } => w!(self, "&mut "),
+                    BorrowKind::Unique => w!(self, "&uniq "),
+                    BorrowKind::Mut { .. } => w!(self, "&mut "),
                 }
                 self.place(p);
             }
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index a0237e3b90..2d8811cf5e 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -68,7 +68,7 @@ use hir_ty::{
     known_const_to_ast,
     layout::{Layout as TyLayout, RustcEnumVariantIdx, RustcFieldIdx, TagEncoding},
     method_resolution::{self, TyFingerprint},
-    mir::{interpret_mir, MutBorrowKind},
+    mir::interpret_mir,
     primitive::UintTy,
     traits::FnTrait,
     AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId, GenericArg,
@@ -3754,12 +3754,12 @@ impl ClosureCapture {
             hir_ty::CaptureKind::ByRef(
                 hir_ty::mir::BorrowKind::Shallow | hir_ty::mir::BorrowKind::Shared,
             ) => CaptureKind::SharedRef,
-            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut {
-                kind: MutBorrowKind::ClosureCapture,
-            }) => CaptureKind::UniqueSharedRef,
-            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut {
-                kind: MutBorrowKind::Default | MutBorrowKind::TwoPhasedBorrow,
-            }) => CaptureKind::MutableRef,
+            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Unique) => {
+                CaptureKind::UniqueSharedRef
+            }
+            hir_ty::CaptureKind::ByRef(hir_ty::mir::BorrowKind::Mut { .. }) => {
+                CaptureKind::MutableRef
+            }
             hir_ty::CaptureKind::ByValue => CaptureKind::Move,
         }
     }
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 14dbe69240..ef4ed90ce3 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -86,6 +86,7 @@
 //! syntax nodes against this specific crate.
 
 use base_db::FileId;
+use either::Either;
 use hir_def::{
     child_by_source::ChildBySource,
     dyn_map::{
@@ -93,9 +94,9 @@ use hir_def::{
         DynMap,
     },
     hir::{BindingId, LabelId},
-    AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId, FieldId,
-    FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId,
-    StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId,
+    AdtId, BlockId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, ExternCrateId,
+    FieldId, FunctionId, GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId,
+    StaticId, StructId, TraitAliasId, TraitId, TypeAliasId, TypeParamId, UnionId, UseId, VariantId,
 };
 use hir_expand::{attrs::AttrId, name::AsName, HirFileId, HirFileIdExt, MacroCallId};
 use rustc_hash::FxHashMap;
@@ -131,15 +132,19 @@ impl SourceToDefCtx<'_, '_> {
         mods
     }
 
-    pub(super) fn module_to_def(&self, src: InFile<ast::Module>) -> Option<ModuleId> {
+    pub(super) fn module_to_def(&mut self, src: InFile<ast::Module>) -> Option<ModuleId> {
         let _p = tracing::span!(tracing::Level::INFO, "module_to_def");
         let parent_declaration = src
             .syntax()
             .ancestors_with_macros_skip_attr_item(self.db.upcast())
-            .find_map(|it| it.map(ast::Module::cast).transpose());
+            .find_map(|it| it.map(Either::<ast::Module, ast::BlockExpr>::cast).transpose())
+            .map(|it| it.transpose());
 
         let parent_module = match parent_declaration {
-            Some(parent_declaration) => self.module_to_def(parent_declaration),
+            Some(Either::Right(parent_block)) => self
+                .block_to_def(parent_block)
+                .map(|block| self.db.block_def_map(block).root_module_id()),
+            Some(Either::Left(parent_declaration)) => self.module_to_def(parent_declaration),
             None => {
                 let file_id = src.file_id.original_file(self.db.upcast());
                 self.file_to_def(file_id).first().copied()
@@ -197,6 +202,9 @@ impl SourceToDefCtx<'_, '_> {
     pub(super) fn tuple_field_to_def(&mut self, src: InFile<ast::TupleField>) -> Option<FieldId> {
         self.to_def(src, keys::TUPLE_FIELD)
     }
+    pub(super) fn block_to_def(&mut self, src: InFile<ast::BlockExpr>) -> Option<BlockId> {
+        self.to_def(src, keys::BLOCK)
+    }
     pub(super) fn enum_variant_to_def(
         &mut self,
         src: InFile<ast::Variant>,
diff --git a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
index 87932bf989..6d3dcf31ab 100644
--- a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
+++ b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -80,21 +80,6 @@ fn foo() {
         );
     }
 
-    #[test]
-    fn replace_filter_map_next_dont_work_for_not_sized_issues_16596() {
-        check_diagnostics(
-            r#"
-//- minicore: iterators
-fn foo() {
-    let mut j = [0].into_iter();
-    let i: &mut dyn Iterator<Item = i32>  = &mut j;
-    let dummy_fn = |v| (v > 0).then_some(v + 1);
-    let _res = i.filter_map(dummy_fn).next();
-}
-"#,
-        );
-    }
-
     #[test]
     fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
         check_diagnostics(
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index e7c1b4497e..96c7c47559 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -342,9 +342,11 @@ fn highlight_name(
 
 fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
     fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
-        use std::{collections::hash_map::DefaultHasher, hash::Hasher};
+        use ide_db::FxHasher;
 
-        let mut hasher = DefaultHasher::new();
+        use std::hash::Hasher;
+
+        let mut hasher = FxHasher::default();
         x.hash(&mut hasher);
         hasher.finish()
     }
diff --git a/crates/toolchain/src/lib.rs b/crates/toolchain/src/lib.rs
index 793138588a..ae71b6700c 100644
--- a/crates/toolchain/src/lib.rs
+++ b/crates/toolchain/src/lib.rs
@@ -63,17 +63,21 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
     // The current implementation checks three places for an executable to use:
     // 1) Appropriate environment variable (erroring if this is set but not a usable executable)
     //      example: for cargo, this checks $CARGO environment variable; for rustc, $RUSTC; etc
-    // 2) `$CARGO_HOME/bin/<executable_name>`
+    // 2) `<executable_name>`
+    //      example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
+    // 3) `$CARGO_HOME/bin/<executable_name>`
     //      where $CARGO_HOME defaults to ~/.cargo (see https://doc.rust-lang.org/cargo/guide/cargo-home.html)
     //      example: for cargo, this tries $CARGO_HOME/bin/cargo, or ~/.cargo/bin/cargo if $CARGO_HOME is unset.
     //      It seems that this is a reasonable place to try for cargo, rustc, and rustup
-    // 3) `<executable_name>`
-    //      example: for cargo, this tries just `cargo`, which will succeed if `cargo` is on the $PATH
     let env_var = executable_name.to_ascii_uppercase();
     if let Some(path) = env::var_os(env_var) {
         return path.into();
     }
 
+    if lookup_in_path(executable_name) {
+        return executable_name.into();
+    }
+
     if let Some(mut path) = get_cargo_home() {
         path.push("bin");
         path.push(executable_name);
@@ -82,10 +86,6 @@ fn get_path_for_executable(executable_name: &'static str) -> PathBuf {
         }
     }
 
-    if lookup_in_path(executable_name) {
-        return executable_name.into();
-    }
-
     executable_name.into()
 }
 
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 8bc11fd481..9e9ea25779 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -337,14 +337,14 @@ You can also pass LSP settings to the server:
 [source,vim]
 ----
 lua << EOF
-local lspconfig = require'lspconfig'
+local nvim_lsp = require'lspconfig'
 
 local on_attach = function(client)
     require'completion'.on_attach(client)
 end
 
-lspconfig.rust_analyzer.setup({
-    on_attach = on_attach,
+nvim_lsp.rust_analyzer.setup({
+    on_attach=on_attach,
     settings = {
         ["rust-analyzer"] = {
             imports = {
@@ -367,19 +367,6 @@ lspconfig.rust_analyzer.setup({
 EOF
 ----
 
-If you're running Neovim 0.10 or later, you can enable inlay hints via `on_attach`:
-
-[source,vim]
-----
-lspconfig.rust_analyzer.setup({
-    on_attach = function(client, bufnr)
-        vim.lsp.inlay_hint.enable(bufnr)
-    end
-})
-----
-
-Note that the hints are only visible after `rust-analyzer` has finished loading **and** you have to edit the file to trigger a re-render.
-
 See https://sharksforarms.dev/posts/neovim-rust/ for more tips on getting started.
 
 Check out https://github.com/mrcjkb/rustaceanvim for a batteries included rust-analyzer setup for Neovim.


Test patch:
diff --git a/crates/hir-def/src/body/tests/block.rs b/crates/hir-def/src/body/tests/block.rs
index 44eeed9e3f..985c6387ba 100644
--- a/crates/hir-def/src/body/tests/block.rs
+++ b/crates/hir-def/src/body/tests/block.rs
@@ -298,6 +298,40 @@ pub mod cov_mark {
     );
 }
 
+#[test]
+fn macro_exported_in_block_mod() {
+    check_at(
+        r#"
+#[macro_export]
+macro_rules! foo {
+    () => { pub struct FooWorks; };
+}
+macro_rules! bar {
+    () => { pub struct BarWorks; };
+}
+fn main() {
+    mod module {
+        foo!();
+        bar!();
+        $0
+    }
+}
+"#,
+        expect![[r#"
+            block scope
+            module: t
+
+            block scope::module
+            BarWorks: t v
+            FooWorks: t v
+
+            crate
+            foo: m
+            main: v
+        "#]],
+    );
+}
+
 #[test]
 fn macro_resolve_legacy() {
     check_at(
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html
new file mode 100644
index 0000000000..977d18c6b7
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_block_mod_items.html
@@ -0,0 +1,64 @@
+
+<style>
+body                { margin: 0; }
+pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padding: 0.4em; }
+
+.lifetime           { color: #DFAF8F; font-style: italic; }
+.label              { color: #DFAF8F; font-style: italic; }
+.comment            { color: #7F9F7F; }
+.documentation      { color: #629755; }
+.intra_doc_link     { font-style: italic; }
+.injected           { opacity: 0.65 ; }
+.struct, .enum      { color: #7CB8BB; }
+.enum_variant       { color: #BDE0F3; }
+.string_literal     { color: #CC9393; }
+.field              { color: #94BFF3; }
+.function           { color: #93E0E3; }
+.function.unsafe    { color: #BC8383; }
+.trait.unsafe       { color: #BC8383; }
+.operator.unsafe    { color: #BC8383; }
+.mutable.unsafe     { color: #BC8383; text-decoration: underline; }
+.keyword.unsafe     { color: #BC8383; font-weight: bold; }
+.macro.unsafe       { color: #BC8383; }
+.parameter          { color: #94BFF3; }
+.text               { color: #DCDCCC; }
+.type               { color: #7CB8BB; }
+.builtin_type       { color: #8CD0D3; }
+.type_param         { color: #DFAF8F; }
+.attribute          { color: #94BFF3; }
+.numeric_literal    { color: #BFEBBF; }
+.bool_literal       { color: #BFE6EB; }
+.macro              { color: #94BFF3; }
+.derive             { color: #94BFF3; font-style: italic; }
+.module             { color: #AFD8AF; }
+.value_param        { color: #DCDCCC; }
+.variable           { color: #DCDCCC; }
+.format_specifier   { color: #CC696B; }
+.mutable            { text-decoration: underline; }
+.escape_sequence    { color: #94BFF3; }
+.keyword            { color: #F0DFAF; font-weight: bold; }
+.control            { font-style: italic; }
+.reference          { font-style: italic; font-weight: bold; }
+
+.invalid_escape_sequence { color: #FC5555; text-decoration: wavy underline; }
+.unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
+</style>
+<pre><code><span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">foo</span> <span class="brace">{</span>
+    <span class="parenthesis">(</span><span class="punctuation">$</span>foo<span class="colon">:</span>ident<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span>
+        <span class="keyword">mod</span> y <span class="brace">{</span>
+            <span class="keyword">struct</span> <span class="punctuation">$</span>foo<span class="semicolon">;</span>
+        <span class="brace">}</span>
+    <span class="brace">}</span><span class="semicolon">;</span>
+<span class="brace">}</span>
+<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+    <span class="macro">foo</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="struct declaration macro">Foo</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="keyword">mod</span> <span class="module declaration">module</span> <span class="brace">{</span>
+        <span class="comment">// FIXME: IDE layer has this unresolved</span>
+        <span class="unresolved_reference">foo</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">Bar</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+        <span class="keyword">fn</span> <span class="function declaration">func</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+            <span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="brace">{</span>
+                <span class="keyword">struct</span> <span class="struct declaration">Innerest</span><span class="angle">&lt;</span><span class="keyword">const</span> <span class="const_param declaration">C</span><span class="colon">:</span> <span class="unresolved_reference">usize</span><span class="angle">&gt;</span> <span class="brace">{</span> <span class="field declaration">field</span><span class="colon">:</span> <span class="bracket">[</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> <span class="brace">{</span><span class="const_param">C</span><span class="brace">}</span><span class="bracket">]</span> <span class="brace">}</span>
+            <span class="brace">}</span>
+        <span class="brace">}</span>
+    <span class="brace">}</span>
+<span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
index ec18c3ea1f..7ee7b338c1 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_rainbow.html
@@ -44,14 +44,14 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 .unresolved_reference    { color: #FC5555; text-decoration: wavy underline; }
 </style>
 <pre><code><span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
-    <span class="keyword">let</span> <span class="variable declaration reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
-    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="2705725358298919760" style="color: hsl(76,47%,83%);">x</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
-    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="3365759661443752373" style="color: hsl(15,86%,51%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration reference" data-binding-hash="8384512769119783714" style="color: hsl(59,93%,58%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="17360984456076382725" style="color: hsl(95,79%,86%);">x</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8384512769119783714" style="color: hsl(59,93%,58%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="17186414787327620935" style="color: hsl(196,64%,89%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="8384512769119783714" style="color: hsl(59,93%,58%);">hello</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
-    <span class="keyword">let</span> <span class="variable declaration reference" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="semicolon">;</span>
-    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="6717528807933952652" style="color: hsl(90,74%,79%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="794745962933817518" style="color: hsl(127,71%,87%);">x</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration reference" data-binding-hash="4786021388930833562" style="color: hsl(137,61%,87%);">x</span> <span class="operator">=</span> <span class="string_literal">"other color please!"</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="variable declaration" data-binding-hash="18017815841345165192" style="color: hsl(39,76%,89%);">y</span> <span class="operator">=</span> <span class="variable reference" data-binding-hash="4786021388930833562" style="color: hsl(137,61%,87%);">x</span><span class="operator">.</span><span class="unresolved_reference">to_string</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 <span class="brace">}</span>
 
 <span class="keyword">fn</span> <span class="function declaration">bar</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
-    <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable reference" data-binding-hash="8121853618659664005" style="color: hsl(273,88%,88%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
+    <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable reference" data-binding-hash="8384512769119783714" style="color: hsl(59,93%,58%);">hello</span> <span class="operator">=</span> <span class="string_literal">"hello"</span><span class="semicolon">;</span>
 <span class="brace">}</span></code></pre>
\ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 864c6d1cad..6fed7d783e 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -993,10 +993,6 @@ pub struct Struct;
 }
 
 #[test]
-#[cfg_attr(
-    not(all(unix, target_pointer_width = "64")),
-    ignore = "depends on `DefaultHasher` outputs"
-)]
 fn test_rainbow_highlighting() {
     check_highlighting(
         r#"
@@ -1018,6 +1014,35 @@ fn bar() {
     );
 }
 
+#[test]
+fn test_block_mod_items() {
+    check_highlighting(
+        r#"
+macro_rules! foo {
+    ($foo:ident) => {
+        mod y {
+            struct $foo;
+        }
+    };
+}
+fn main() {
+    foo!(Foo);
+    mod module {
+        // FIXME: IDE layer has this unresolved
+        foo!(Bar);
+        fn func() {
+            mod inner {
+                struct Innerest<const C: usize> { field: [(); {C}] }
+            }
+        }
+    }
+}
+"#,
+        expect_file!["./test_data/highlight_block_mod_items.html"],
+        false,
+    );
+}
+
 #[test]
 fn test_ranges() {
     let (analysis, file_id) = fixture::file(