Repository: rust-lang/rust-analyzer
Pull request #17898 (2024-08-15 14:25:22)

internal: Improve macro token mapping heuristics
Fixes https://github.com/rust-lang/rust-analyzer/issues/16235

bors: :umbrella: The latest upstream changes (presumably #17905) made this pull request unmergeable. Please [resolve the merge conflicts](https://rustc-dev-guide.rust-lang.org/git.html#rebasing-and-conflicts).

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 56e80e11e7..d8f9b2b7f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -184,6 +184,8 @@ style = { level = "warn", priority = -1 }
 suspicious = { level = "warn", priority = -1 }
 
 ## allow following lints
+# subjective
+single_match = "allow"
 # () makes a fine error in most cases
 result_unit_err = "allow"
 # We don't expose public APIs that matter like this
diff --git a/crates/hir-expand/src/files.rs b/crates/hir-expand/src/files.rs
index 20f484f672..d41f69812e 100644
--- a/crates/hir-expand/src/files.rs
+++ b/crates/hir-expand/src/files.rs
@@ -461,3 +461,12 @@ impl<N: AstNode> InFile<N> {
         Some(InRealFile::new(file_id, value))
     }
 }
+
+impl<T> InFile<T> {
+    pub fn into_real_file(self) -> Result<InRealFile<T>, InFile<T>> {
+        match self.file_id.repr() {
+            HirFileIdRepr::FileId(file_id) => Ok(InRealFile { file_id, value: self.value }),
+            HirFileIdRepr::MacroFile(_) => Err(self),
+        }
+    }
+}
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 2bea902626..741c2b2ebf 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -279,6 +279,7 @@ pub enum MacroCallKind {
 }
 
 pub trait HirFileIdExt {
+    fn edition(self, db: &dyn ExpandDatabase) -> Edition;
     /// Returns the original file of this macro call hierarchy.
     fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId;
 
@@ -293,6 +294,12 @@ pub trait HirFileIdExt {
 }
 
 impl HirFileIdExt for HirFileId {
+    fn edition(self, db: &dyn ExpandDatabase) -> Edition {
+        match self.repr() {
+            HirFileIdRepr::FileId(file_id) => file_id.edition(),
+            HirFileIdRepr::MacroFile(m) => m.macro_call_id.lookup(db).def.edition,
+        }
+    }
     fn original_file(self, db: &dyn ExpandDatabase) -> EditionedFileId {
         let mut file_id = self;
         loop {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 57f810c7c7..6e26af55af 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -93,8 +93,7 @@ pub use crate::{
     diagnostics::*,
     has_source::HasSource,
     semantics::{
-        DescendPreference, PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo,
-        VisibleTraits,
+        PathResolution, Semantics, SemanticsImpl, SemanticsScope, TypeInfo, VisibleTraits,
     },
 };
 pub use hir_ty::method_resolution::TyFingerprint;
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index d086aee428..3d6c985043 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -4,6 +4,7 @@ mod source_to_def;
 
 use std::{
     cell::RefCell,
+    convert::Infallible,
     fmt, iter, mem,
     ops::{self, ControlFlow, Not},
 };
@@ -48,11 +49,7 @@ use crate::{
     Variant, VariantDef,
 };
 
-pub enum DescendPreference {
-    SameText,
-    SameKind,
-    None,
-}
+const CONTINUE_NO_BREAKS: ControlFlow<Infallible, ()> = ControlFlow::Continue(());
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum PathResolution {
@@ -182,6 +179,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
 
     /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
     /// descend it and find again
+    // FIXME: Rethink this API
     pub fn find_node_at_offset_with_descend<N: AstNode>(
         &self,
         node: &SyntaxNode,
@@ -190,8 +188,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
         self.imp.descend_node_at_offset(node, offset).flatten().find_map(N::cast)
     }
 
-    /// Find an AstNode by offset inside SyntaxNode, if it is inside *MacroCall*,
+    /// Find an AstNode by offset inside SyntaxNode, if it is inside an attribute macro call,
     /// descend it and find again
+    // FIXME: Rethink this API
     pub fn find_nodes_at_offset_with_descend<'slf, N: AstNode + 'slf>(
         &'slf self,
         node: &SyntaxNode,
@@ -545,51 +544,53 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    /// Retrieves all the formatting parts of the format_args! template string.
     pub fn as_format_args_parts(
         &self,
         string: &ast::String,
     ) -> Option<Vec<(TextRange, Option<PathResolution>)>> {
-        if let Some(quote) = string.open_quote_text_range() {
-            return self
-                .descend_into_macros(DescendPreference::SameText, string.syntax().clone())
-                .into_iter()
-                .find_map(|token| {
-                    let string = ast::String::cast(token)?;
-                    let literal =
-                        string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
-                    let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
-                    let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
-                    let format_args = self.wrap_node_infile(format_args);
-                    let res = source_analyzer
-                        .as_format_args_parts(self.db, format_args.as_ref())?
-                        .map(|(range, res)| (range + quote.end(), res))
-                        .collect();
-                    Some(res)
-                });
-        }
-        None
+        let quote = string.open_quote_text_range()?;
+
+        let token = self.wrap_token_infile(string.syntax().clone()).into_real_file().ok()?;
+        self.descend_into_macros_breakable(token, |token| {
+            (|| {
+                let token = token.value;
+                let string = ast::String::cast(token)?;
+                let literal =
+                    string.syntax().parent().filter(|it| it.kind() == SyntaxKind::LITERAL)?;
+                let format_args = ast::FormatArgsExpr::cast(literal.parent()?)?;
+                let source_analyzer = self.analyze_no_infer(format_args.syntax())?;
+                let format_args = self.wrap_node_infile(format_args);
+                let res = source_analyzer
+                    .as_format_args_parts(self.db, format_args.as_ref())?
+                    .map(|(range, res)| (range + quote.end(), res))
+                    .collect();
+                Some(res)
+            })()
+            .map_or(ControlFlow::Continue(()), ControlFlow::Break)
+        })
     }
 
+    /// Retrieves the formatting part of the format_args! template string at the given offset.
     pub fn check_for_format_args_template(
         &self,
         original_token: SyntaxToken,
         offset: TextSize,
     ) -> Option<(TextRange, Option<PathResolution>)> {
-        if let Some(original_string) = ast::String::cast(original_token.clone()) {
-            if let Some(quote) = original_string.open_quote_text_range() {
-                return self
-                    .descend_into_macros(DescendPreference::SameText, original_token)
-                    .into_iter()
-                    .find_map(|token| {
-                        self.resolve_offset_in_format_args(
-                            ast::String::cast(token)?,
-                            offset.checked_sub(quote.end())?,
-                        )
-                    })
-                    .map(|(range, res)| (range + quote.end(), res));
-            }
-        }
-        None
+        let original_string = ast::String::cast(original_token.clone())?;
+        let original_token = self.wrap_token_infile(original_token).into_real_file().ok()?;
+        let quote = original_string.open_quote_text_range()?;
+        self.descend_into_macros_breakable(original_token, |token| {
+            (|| {
+                let token = token.value;
+                self.resolve_offset_in_format_args(
+                    ast::String::cast(token)?,
+                    offset.checked_sub(quote.end())?,
+                )
+                .map(|(range, res)| (range + quote.end(), res))
+            })()
+            .map_or(ControlFlow::Continue(()), ControlFlow::Break)
+        })
     }
 
     fn resolve_offset_in_format_args(
@@ -619,30 +620,37 @@ impl<'db> SemanticsImpl<'db> {
             Some(it) => it,
             None => return res,
         };
+        let file = self.find_file(node.syntax());
+        let Some(file_id) = file.file_id.file_id() else {
+            return res;
+        };
 
         if first == last {
             // node is just the token, so descend the token
-            self.descend_into_macros_impl(first, &mut |InFile { value, .. }| {
-                if let Some(node) = value
-                    .parent_ancestors()
-                    .take_while(|it| it.text_range() == value.text_range())
-                    .find_map(N::cast)
-                {
-                    res.push(node)
-                }
-                ControlFlow::Continue(())
-            });
+            self.descend_into_macros_impl(
+                InRealFile::new(file_id, first),
+                &mut |InFile { value, .. }| {
+                    if let Some(node) = value
+                        .parent_ancestors()
+                        .take_while(|it| it.text_range() == value.text_range())
+                        .find_map(N::cast)
+                    {
+                        res.push(node)
+                    }
+                    CONTINUE_NO_BREAKS
+                },
+            );
         } else {
             // Descend first and last token, then zip them to look for the node they belong to
             let mut scratch: SmallVec<[_; 1]> = smallvec![];
-            self.descend_into_macros_impl(first, &mut |token| {
+            self.descend_into_macros_impl(InRealFile::new(file_id, first), &mut |token| {
                 scratch.push(token);
-                ControlFlow::Continue(())
+                CONTINUE_NO_BREAKS
             });
 
             let mut scratch = scratch.into_iter();
             self.descend_into_macros_impl(
-                last,
+                InRealFile::new(file_id, last),
                 &mut |InFile { value: last, file_id: last_fid }| {
                     if let Some(InFile { value: first, file_id: first_fid }) = scratch.next() {
                         if first_fid == last_fid {
@@ -659,130 +667,122 @@ impl<'db> SemanticsImpl<'db> {
                             }
                         }
                     }
-                    ControlFlow::Continue(())
+                    CONTINUE_NO_BREAKS
                 },
             );
         }
         res
     }
 
-    /// Descend the token into its macro call if it is part of one, returning the tokens in the
-    /// expansion that it is associated with.
-    pub fn descend_into_macros(
+    pub fn descend_into_macros_cb(
         &self,
-        mode: DescendPreference,
         token: SyntaxToken,
-    ) -> SmallVec<[SyntaxToken; 1]> {
-        enum Dp<'t> {
-            SameText(&'t str),
-            SameKind(SyntaxKind),
-            None,
+        mut cb: impl FnMut(InFile<SyntaxToken>),
+    ) {
+        if let Ok(token) = self.wrap_token_infile(token).into_real_file() {
+            self.descend_into_macros_impl(token, &mut |t| {
+                cb(t);
+                CONTINUE_NO_BREAKS
+            });
         }
-        let fetch_kind = |token: &SyntaxToken| match token.parent() {
-            Some(node) => match node.kind() {
-                kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
-                _ => token.kind(),
-            },
-            None => token.kind(),
-        };
-        let mode = match mode {
-            DescendPreference::SameText => Dp::SameText(token.text()),
-            DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
-            DescendPreference::None => Dp::None,
-        };
+    }
+
+    pub fn descend_into_macros(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
         let mut res = smallvec![];
-        self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
-            let is_a_match = match mode {
-                Dp::SameText(text) => value.text() == text,
-                Dp::SameKind(preferred_kind) => {
-                    let kind = fetch_kind(&value);
-                    kind == preferred_kind
-                        // special case for derive macros
-                        || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
-                }
-                Dp::None => true,
-            };
-            if is_a_match {
-                res.push(value);
-            }
-            ControlFlow::Continue(())
-        });
+        if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
+            self.descend_into_macros_impl(token, &mut |t| {
+                res.push(t.value);
+                CONTINUE_NO_BREAKS
+            });
+        }
         if res.is_empty() {
             res.push(token);
         }
         res
     }
 
-    pub fn descend_into_macros_single(
+    pub fn descend_into_macros_breakable<T>(
         &self,
-        mode: DescendPreference,
-        token: SyntaxToken,
-    ) -> SyntaxToken {
-        enum Dp<'t> {
-            SameText(&'t str),
-            SameKind(SyntaxKind),
-            None,
-        }
-        let fetch_kind = |token: &SyntaxToken| match token.parent() {
-            Some(node) => match node.kind() {
-                kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
-                _ => token.kind(),
-            },
-            None => token.kind(),
-        };
-        let mode = match mode {
-            DescendPreference::SameText => Dp::SameText(token.text()),
-            DescendPreference::SameKind => Dp::SameKind(fetch_kind(&token)),
-            DescendPreference::None => Dp::None,
-        };
-        let mut res = token.clone();
-        self.descend_into_macros_impl(token.clone(), &mut |InFile { value, .. }| {
-            let is_a_match = match mode {
-                Dp::SameText(text) => value.text() == text,
-                Dp::SameKind(preferred_kind) => {
-                    let kind = fetch_kind(&value);
-                    kind == preferred_kind
-                        // special case for derive macros
-                        || (preferred_kind == SyntaxKind::IDENT && kind == SyntaxKind::NAME_REF)
-                }
-                Dp::None => true,
-            };
-            res = value;
-            if is_a_match {
-                ControlFlow::Break(())
-            } else {
-                ControlFlow::Continue(())
+        token: InRealFile<SyntaxToken>,
+        mut cb: impl FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
+    ) -> Option<T> {
+        self.descend_into_macros_impl(token.clone(), &mut cb)
+    }
+
+    /// Descends the token into expansions, returning the tokens that matches the input
+    /// token's [`SyntaxKind`] and text.
+    pub fn descend_into_macros_exact(&self, token: SyntaxToken) -> SmallVec<[SyntaxToken; 1]> {
+        let mut r = smallvec![];
+        let text = token.text();
+        let kind = token.kind();
+
+        self.descend_into_macros_cb(token.clone(), |InFile { value, file_id: _ }| {
+            let mapped_kind = value.kind();
+            let any_ident_match = || kind.is_any_identifier() && value.kind().is_any_identifier();
+            let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
+            if matches {
+                r.push(value);
             }
         });
-        res
+        if r.is_empty() {
+            r.push(token);
+        }
+        r
+    }
+
+    /// Descends the token into expansions, returning the first token that matches the input
+    /// token's [`SyntaxKind`] and text.
+    pub fn descend_into_macros_single_exact(&self, token: SyntaxToken) -> SyntaxToken {
+        let text = token.text();
+        let kind = token.kind();
+        if let Ok(token) = self.wrap_token_infile(token.clone()).into_real_file() {
+            self.descend_into_macros_breakable(token.clone(), |InFile { value, file_id: _ }| {
+                let mapped_kind = value.kind();
+                let any_ident_match =
+                    || kind.is_any_identifier() && value.kind().is_any_identifier();
+                let matches = (kind == mapped_kind || any_ident_match()) && text == value.text();
+                if matches {
+                    ControlFlow::Break(value)
+                } else {
+                    ControlFlow::Continue(())
+                }
+            })
+        } else {
+            None
+        }
+        .unwrap_or(token)
     }
 
-    fn descend_into_macros_impl(
+    fn descend_into_macros_impl<T>(
         &self,
-        token: SyntaxToken,
-        f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
-    ) {
+        InRealFile { value: token, file_id }: InRealFile<SyntaxToken>,
+        f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<T>,
+    ) -> Option<T> {
         let _p = tracing::info_span!("descend_into_macros_impl").entered();
-        let (sa, span, file_id) =
-            match token.parent().and_then(|parent| self.analyze_no_infer(&parent)) {
-                Some(sa) => match sa.file_id.file_id() {
-                    Some(file_id) => (
-                        sa,
-                        self.db.real_span_map(file_id).span_for_range(token.text_range()),
-                        file_id.into(),
-                    ),
-                    None => {
-                        stdx::never!();
-                        return;
-                    }
-                },
-                None => return,
-            };
+        let (sa, span, file_id) = token
+            .parent()
+            .and_then(|parent| {
+                self.analyze_impl(InRealFile::new(file_id, &parent).into(), None, false)
+            })
+            .and_then(|sa| {
+                let file_id = sa.file_id.file_id()?;
+                Some((
+                    sa,
+                    self.db.real_span_map(file_id).span_for_range(token.text_range()),
+                    HirFileId::from(file_id),
+                ))
+            })?;
 
         let mut m_cache = self.macro_call_cache.borrow_mut();
         let def_map = sa.resolver.def_map();
 
+        // A stack of tokens to process, along with the file they came from
+        // These are tracked to know which macro calls we still have to look into
+        // the tokens themselves aren't that interesting as the span that is being used to map
+        // things down never changes.
         let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
+
+        // Process the expansion of a call, pushing all tokens with our span in the expansion back onto our stack
         let process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
             let InMacroFile { file_id, value: mapped_tokens } = self.with_ctx(|ctx| {
                 Some(
@@ -809,7 +809,13 @@ impl<'db> SemanticsImpl<'db> {
             res
         };
 
-        while let Some((file_id, mut tokens)) = stack.pop() {
+        // Filters out all tokens that contain the given range (usually the macro call), any such
+        // token is redundant as the corresponding macro call has already been processed
+        let filter_duplicates = |tokens: &mut SmallVec<_>, range: TextRange| {
+            tokens.retain(|t: &mut SyntaxToken| !range.contains_range(t.text_range()))
+        };
+
+        while let Some((expansion, ref mut tokens)) = stack.pop() {
             while let Some(token) = tokens.pop() {
                 let was_not_remapped = (|| {
                     // First expand into attribute invocations
@@ -817,7 +823,7 @@ impl<'db> SemanticsImpl<'db> {
                         token.parent_ancestors().filter_map(ast::Item::cast).find_map(|item| {
                             // Don't force populate the dyn cache for items that don't have an attribute anyways
                             item.attrs().next()?;
-                            Some((ctx.item_to_macro_call(InFile::new(file_id, &item))?, item))
+                            Some((ctx.item_to_macro_call(InFile::new(expansion, &item))?, item))
                         })
                     });
                     if let Some((call_id, item)) = containing_attribute_macro_call {
@@ -849,9 +855,7 @@ impl<'db> SemanticsImpl<'db> {
                             })
                             .unwrap_or_else(|| text_range.start());
                         let text_range = TextRange::new(start, text_range.end());
-                        // remove any other token in this macro input, all their mappings are the
-                        // same as this one
-                        tokens.retain(|t| !text_range.contains_range(t.text_range()));
+                        filter_duplicates(tokens, text_range);
                         return process_expansion_for_token(&mut stack, file_id);
                     }
 
@@ -862,6 +866,7 @@ impl<'db> SemanticsImpl<'db> {
                         .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
                         .last()?;
                     match tt {
+                        // function-like macro call
                         Either::Left(tt) => {
                             if tt.left_delimiter_token().map_or(false, |it| it == token) {
                                 return None;
@@ -870,7 +875,7 @@ impl<'db> SemanticsImpl<'db> {
                                 return None;
                             }
                             let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
-                            let mcall = InFile::new(file_id, macro_call);
+                            let mcall = InFile::new(expansion, macro_call);
                             let file_id = match m_cache.get(&mcall) {
                                 Some(&it) => it,
                                 None => {
@@ -888,9 +893,7 @@ impl<'db> SemanticsImpl<'db> {
                                 }
                             };
                             let text_range = tt.syntax().text_range();
-                            // remove any other token in this macro input, all their mappings are the
-                            // same as this one
-                            tokens.retain(|t| !text_range.contains_range(t.text_range()));
+                            filter_duplicates(tokens, text_range);
 
                             process_expansion_for_token(&mut stack, file_id).or(file_id
                                 .eager_arg(self.db.upcast())
@@ -899,6 +902,7 @@ impl<'db> SemanticsImpl<'db> {
                                     process_expansion_for_token(&mut stack, arg.as_macro_file())
                                 }))
                         }
+                        // derive or derive helper
                         Either::Right(meta) => {
                             // attribute we failed expansion for earlier, this might be a derive invocation
                             // or derive helper attribute
@@ -910,8 +914,8 @@ impl<'db> SemanticsImpl<'db> {
                                         // so try downmapping the token into the pseudo derive expansion
                                         // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
                                         ctx.attr_to_derive_macro_call(
-                                            InFile::new(file_id, &adt),
-                                            InFile::new(file_id, attr.clone()),
+                                            InFile::new(expansion, &adt),
+                                            InFile::new(expansion, attr.clone()),
                                         )
                                         .map(|(_, call_id, _)| call_id)
                                     });
@@ -945,28 +949,29 @@ impl<'db> SemanticsImpl<'db> {
                                     )
                                 }
                             }?;
-                            if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+                            if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(expansion, &adt))) {
                                 return None;
                             }
                             let attr_name =
                                 attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
-                            // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+                            // Not an attribute, nor a derive, so it's either an intert attribute or a derive helper
                             // Try to resolve to a derive helper and downmap
-                            let id = self.db.ast_id_map(file_id).ast_id(&adt);
+                            let id = self.db.ast_id_map(expansion).ast_id(&adt);
                             let helpers =
-                                def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
+                                def_map.derive_helpers_in_scope(InFile::new(expansion, id))?;
 
                             if !helpers.is_empty() {
                                 let text_range = attr.syntax().text_range();
-                                // remove any other token in this macro input, all their mappings are the
-                                // same as this
-                                tokens.retain(|t| !text_range.contains_range(t.text_range()));
+                                filter_duplicates(tokens, text_range);
                             }
 
                             let mut res = None;
                             for (.., derive) in
                                 helpers.iter().filter(|(helper, ..)| *helper == attr_name)
                             {
+                                // as there may be multiple derives registering the same helper
+                                // name, we gotta make sure to call this for all of them!
+                                // FIXME: We need to call `f` for all of them as well though!
                                 res = res.or(process_expansion_for_token(
                                     &mut stack,
                                     derive.as_macro_file(),
@@ -978,11 +983,14 @@ impl<'db> SemanticsImpl<'db> {
                 })()
                 .is_none();
 
-                if was_not_remapped && f(InFile::new(file_id, token)).is_break() {
-                    break;
+                if was_not_remapped {
+                    if let ControlFlow::Break(b) = f(InFile::new(expansion, token)) {
+                        return Some(b);
+                    }
                 }
             }
         }
+        None
     }
 
     // Note this return type is deliberate as [`find_nodes_at_offset_with_descend`] wants to stop
@@ -995,7 +1003,7 @@ impl<'db> SemanticsImpl<'db> {
         offset: TextSize,
     ) -> impl Iterator<Item = impl Iterator<Item = SyntaxNode> + '_> + '_ {
         node.token_at_offset(offset)
-            .map(move |token| self.descend_into_macros(DescendPreference::None, token))
+            .map(move |token| self.descend_into_macros_exact(token))
             .map(|descendants| {
                 descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
             })
@@ -1414,11 +1422,13 @@ impl<'db> SemanticsImpl<'db> {
 
     /// Returns none if the file of the node is not part of a crate.
     fn analyze(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+        let node = self.find_file(node);
         self.analyze_impl(node, None, true)
     }
 
     /// Returns none if the file of the node is not part of a crate.
     fn analyze_no_infer(&self, node: &SyntaxNode) -> Option<SourceAnalyzer> {
+        let node = self.find_file(node);
         self.analyze_impl(node, None, false)
     }
 
@@ -1427,17 +1437,17 @@ impl<'db> SemanticsImpl<'db> {
         node: &SyntaxNode,
         offset: TextSize,
     ) -> Option<SourceAnalyzer> {
+        let node = self.find_file(node);
         self.analyze_impl(node, Some(offset), false)
     }
 
     fn analyze_impl(
         &self,
-        node: &SyntaxNode,
+        node: InFile<&SyntaxNode>,
         offset: Option<TextSize>,
         infer_body: bool,
     ) -> Option<SourceAnalyzer> {
         let _p = tracing::info_span!("SemanticsImpl::analyze_impl").entered();
-        let node = self.find_file(node);
 
         let container = self.with_ctx(|ctx| ctx.find_container(node))?;
 
@@ -1482,6 +1492,11 @@ impl<'db> SemanticsImpl<'db> {
         InFile::new(file_id, node)
     }
 
+    fn wrap_token_infile(&self, token: SyntaxToken) -> InFile<SyntaxToken> {
+        let InFile { file_id, .. } = self.find_file(&token.parent().unwrap());
+        InFile::new(file_id, token)
+    }
+
     /// Wraps the node in a [`InFile`] with the file id it belongs to.
     fn find_file<'node>(&self, node: &'node SyntaxNode) -> InFile<&'node SyntaxNode> {
         let root_node = find_root(node);
diff --git a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
index 9180d8dfcb..e4d347ef16 100644
--- a/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
+++ b/crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs
@@ -1,11 +1,7 @@
 use crate::{utils, AssistContext, Assists};
-use hir::DescendPreference;
 use ide_db::{
     assists::{AssistId, AssistKind},
-    syntax_helpers::{
-        format_string::is_format_string,
-        format_string_exprs::{parse_format_exprs, Arg},
-    },
+    syntax_helpers::format_string_exprs::{parse_format_exprs, Arg},
 };
 use itertools::Itertools;
 use syntax::{
@@ -40,13 +36,7 @@ pub(crate) fn extract_expressions_from_format_string(
     let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
     let tt_delimiter = tt.left_delimiter_token()?.kind();
 
-    let expanded_t = ast::String::cast(
-        ctx.sema
-            .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()),
-    )?;
-    if !is_format_string(&expanded_t) {
-        return None;
-    }
+    let _ = ctx.sema.as_format_args_parts(&fmt_string)?;
 
     let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
     if extracted_args.is_empty() {
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index cfcb09b9f7..fd379cb88d 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -3,8 +3,8 @@ use std::{iter, ops::RangeInclusive};
 use ast::make;
 use either::Either;
 use hir::{
-    DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef,
-    PathResolution, Semantics, TypeInfo, TypeParam,
+    HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
+    TypeInfo, TypeParam,
 };
 use ide_db::{
     defs::{Definition, NameRefClass},
@@ -834,7 +834,7 @@ impl FunctionBody {
                         .descendants_with_tokens()
                         .filter_map(SyntaxElement::into_token)
                         .filter(|it| matches!(it.kind(), SyntaxKind::IDENT | T![self]))
-                        .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t))
+                        .flat_map(|t| sema.descend_into_macros_exact(t))
                         .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast)));
                 }
             }
diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs
index 63c09af3d6..84fa58d743 100644
--- a/crates/ide-db/src/helpers.rs
+++ b/crates/ide-db/src/helpers.rs
@@ -3,7 +3,7 @@
 use std::collections::VecDeque;
 
 use base_db::SourceRootDatabase;
-use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics};
+use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics};
 use span::{Edition, FileId};
 use syntax::{
     ast::{self, make},
@@ -112,11 +112,12 @@ pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool {
     !db.source_root(source_root_id).is_library
 }
 
+// FIXME: This is a weird function
 pub fn get_definition(
     sema: &Semantics<'_, RootDatabase>,
     token: SyntaxToken,
 ) -> Option<Definition> {
-    for token in sema.descend_into_macros(DescendPreference::None, token) {
+    for token in sema.descend_into_macros_exact(token) {
         let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
         if let Some(&[x]) = def.as_deref() {
             return Some(x);
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 7319b7ac02..197c327ee4 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -9,8 +9,8 @@ use std::mem;
 
 use base_db::{salsa::Database, SourceDatabase, SourceRootDatabase};
 use hir::{
-    sym, AsAssocItem, DefWithBody, DescendPreference, FileRange, HasAttrs, HasSource, HirFileIdExt,
-    InFile, InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
+    sym, AsAssocItem, DefWithBody, FileRange, HasAttrs, HasSource, HirFileIdExt, InFile,
+    InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
 };
 use memchr::memmem::Finder;
 use parser::SyntaxKind;
@@ -549,9 +549,7 @@ impl<'a> FindUsages<'a> {
                     // every textual hit. That function is notoriously
                     // expensive even for things that do not get down mapped
                     // into macros.
-                    sema.descend_into_macros(DescendPreference::None, token)
-                        .into_iter()
-                        .filter_map(|it| it.parent())
+                    sema.descend_into_macros_exact(token).into_iter().filter_map(|it| it.parent())
                 })
         };
 
diff --git a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
index 8ab5a6ede3..c104aa5718 100644
--- a/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
+++ b/crates/ide-db/src/syntax_helpers/format_string_exprs.rs
@@ -31,6 +31,7 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
         .collect()
 }
 
+// FIXME Remove this, we have this information in the HIR now
 /// Parser for a format-like string. It is more allowing in terms of string contents,
 /// as we expect variable placeholders to be filled with expressions.
 ///
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 8709310485..155259a138 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -2,7 +2,7 @@
 
 use std::iter;
 
-use hir::{DescendPreference, Semantics};
+use hir::Semantics;
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     helpers::pick_best_token,
@@ -86,7 +86,7 @@ pub(crate) fn outgoing_calls(
     })?;
     let mut calls = CallLocations::default();
 
-    sema.descend_into_macros(DescendPreference::None, token)
+    sema.descend_into_macros_exact(token)
         .into_iter()
         .filter_map(|it| it.parent_ancestors().nth(1).and_then(ast::Item::cast))
         .filter_map(|item| match item {
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index cad0d06eea..925ae62023 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -10,10 +10,7 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions
 use stdx::format_to;
 use url::Url;
 
-use hir::{
-    db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference,
-    HasAttrs,
-};
+use hir::{db::HirDatabase, sym, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
 use ide_db::{
     base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase},
     defs::{Definition, NameClass, NameRefClass},
@@ -144,7 +141,7 @@ pub(crate) fn external_docs(
         kind if kind.is_trivia() => 0,
         _ => 1,
     })?;
-    let token = sema.descend_into_macros_single(DescendPreference::None, token);
+    let token = sema.descend_into_macros_single_exact(token);
 
     let node = token.parent()?;
     let definition = match_ast! {
@@ -289,7 +286,7 @@ impl DocCommentToken {
         let original_start = doc_token.text_range().start();
         let relative_comment_offset = offset - original_start - prefix_len;
 
-        sema.descend_into_macros(DescendPreference::None, doc_token).into_iter().find_map(|t| {
+        sema.descend_into_macros(doc_token).into_iter().find_map(|t| {
             let (node, descended_prefix_len) = match_ast! {
                 match t {
                     ast::Comment(comment) => (t.parent()?, TextSize::try_from(comment.prefix().len()).ok()?),
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 9788c01544..a939ed214a 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -1,4 +1,4 @@
-use hir::{DescendPreference, InFile, MacroFileIdExt, Semantics};
+use hir::{InFile, MacroFileIdExt, Semantics};
 use ide_db::{
     helpers::pick_best_token, syntax_helpers::insert_whitespace_into_node::insert_ws_into, FileId,
     RootDatabase,
@@ -41,37 +41,30 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
     // struct Bar;
     // ```
 
-    let derive = sema
-        .descend_into_macros(DescendPreference::None, tok.clone())
-        .into_iter()
-        .find_map(|descended| {
-            let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
-            if !macro_file.is_derive_attr_pseudo_expansion(db) {
-                return None;
-            }
+    let derive = sema.descend_into_macros_exact(tok.clone()).into_iter().find_map(|descended| {
+        let macro_file = sema.hir_file_for(&descended.parent()?).macro_file()?;
+        if !macro_file.is_derive_attr_pseudo_expansion(db) {
+            return None;
+        }
 
-            let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
-            // up map out of the #[derive] expansion
-            let InFile { file_id, value: tokens } =
-                hir::InMacroFile::new(macro_file, descended).upmap_once(db);
-            let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
-            let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
-            let expansions = sema.expand_derive_macro(&attr)?;
-            let idx = attr
-                .token_tree()?
-                .token_trees_and_tokens()
-                .filter_map(NodeOrToken::into_token)
-                .take_while(|it| it != &token)
-                .filter(|it| it.kind() == T![,])
-                .count();
-            let expansion = format(
-                db,
-                SyntaxKind::MACRO_ITEMS,
-                position.file_id,
-                expansions.get(idx).cloned()?,
-            );
-            Some(ExpandedMacro { name, expansion })
-        });
+        let name = descended.parent_ancestors().filter_map(ast::Path::cast).last()?.to_string();
+        // up map out of the #[derive] expansion
+        let InFile { file_id, value: tokens } =
+            hir::InMacroFile::new(macro_file, descended).upmap_once(db);
+        let token = sema.parse_or_expand(file_id).covering_element(tokens[0]).into_token()?;
+        let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+        let expansions = sema.expand_derive_macro(&attr)?;
+        let idx = attr
+            .token_tree()?
+            .token_trees_and_tokens()
+            .filter_map(NodeOrToken::into_token)
+            .take_while(|it| it != &token)
+            .filter(|it| it.kind() == T![,])
+            .count();
+        let expansion =
+            format(db, SyntaxKind::MACRO_ITEMS, position.file_id, expansions.get(idx).cloned()?);
+        Some(ExpandedMacro { name, expansion })
+    });
 
     if derive.is_some() {
         return derive;
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 5f6aaeaabb..3d49082f28 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -1,6 +1,6 @@
 use std::iter::successors;
 
-use hir::{DescendPreference, Semantics};
+use hir::Semantics;
 use ide_db::RootDatabase;
 use syntax::{
     algo::{self, skip_trivia_token},
@@ -140,10 +140,8 @@ fn extend_tokens_from_range(
 
     // compute original mapped token range
     let extended = {
-        let fst_expanded =
-            sema.descend_into_macros_single(DescendPreference::None, first_token.clone());
-        let lst_expanded =
-            sema.descend_into_macros_single(DescendPreference::None, last_token.clone());
+        let fst_expanded = sema.descend_into_macros_single_exact(first_token.clone());
+        let lst_expanded = sema.descend_into_macros_single_exact(last_token.clone());
         let mut lca =
             algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
         lca = shallowest_node(&lca);
@@ -157,7 +155,7 @@ fn extend_tokens_from_range(
     let validate = || {
         let extended = &extended;
         move |token: &SyntaxToken| -> bool {
-            let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone());
+            let expanded = sema.descend_into_macros_single_exact(token.clone());
             let parent = match expanded.parent() {
                 Some(it) => it,
                 None => return false,
diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs
index 6076de54eb..6ae9dde84b 100644
--- a/crates/ide/src/goto_declaration.rs
+++ b/crates/ide/src/goto_declaration.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, DescendPreference, Semantics};
+use hir::{AsAssocItem, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -29,7 +29,7 @@ pub(crate) fn goto_declaration(
         .find(|it| matches!(it.kind(), IDENT | T![self] | T![super] | T![crate] | T![Self]))?;
     let range = original_token.text_range();
     let info: Vec<NavigationTarget> = sema
-        .descend_into_macros(DescendPreference::None, original_token)
+        .descend_into_macros(original_token)
         .iter()
         .filter_map(|token| {
             let parent = token.parent()?;
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 6a6fbcb9a6..5769f2cabc 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -5,10 +5,7 @@ use crate::{
     navigation_target::{self, ToNav},
     FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
 };
-use hir::{
-    AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef,
-    Semantics,
-};
+use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
 use ide_db::{
     base_db::{AnchoredPath, FileLoader, SourceDatabase},
     defs::{Definition, IdentClass},
@@ -86,7 +83,7 @@ pub(crate) fn goto_definition(
     }
 
     let navs = sema
-        .descend_into_macros(DescendPreference::None, original_token.clone())
+        .descend_into_macros(original_token.clone())
         .into_iter()
         .filter_map(|token| {
             let parent = token.parent()?;
@@ -251,10 +248,7 @@ pub(crate) fn find_fn_or_blocks(
         None
     };
 
-    sema.descend_into_macros(DescendPreference::None, token.clone())
-        .into_iter()
-        .filter_map(find_ancestors)
-        .collect_vec()
+    sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec()
 }
 
 fn nav_for_exit_points(
@@ -369,7 +363,7 @@ pub(crate) fn find_loops(
         None
     };
 
-    sema.descend_into_macros(DescendPreference::None, token.clone())
+    sema.descend_into_macros(token.clone())
         .into_iter()
         .filter_map(find_ancestors)
         .collect_vec()
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 2eff7796d5..e36c8ee2f3 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, DescendPreference, Impl, Semantics};
+use hir::{AsAssocItem, Impl, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     helpers::pick_best_token,
@@ -10,7 +10,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
 
 // Feature: Go to Implementation
 //
-// Navigates to the impl blocks of types.
+// Navigates to the impl items of types.
 //
 // |===
 // | Editor  | Shortcut
@@ -32,48 +32,55 @@ pub(crate) fn goto_implementation(
         _ => 0,
     })?;
     let range = original_token.text_range();
-    let navs =
-        sema.descend_into_macros_single(DescendPreference::SameText, original_token)
-            .parent()
-            .and_then(ast::NameLike::cast)
-            .and_then(|node| match &node {
-                ast::NameLike::Name(name) => {
-                    NameClass::classify(&sema, name).and_then(|class| match class {
-                        NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
-                        NameClass::PatFieldShorthand { .. } => None,
-                    })
-                }
-                ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
-                    .and_then(|class| match class {
-                        NameRefClass::Definition(def) => Some(def),
-                        NameRefClass::FieldShorthand { .. }
-                        | NameRefClass::ExternCrateShorthand { .. } => None,
-                    }),
-                ast::NameLike::Lifetime(_) => None,
-            })
-            .and_then(|def| {
-                let navs = match def {
-                    Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
-                    Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
-                    Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
-                    Definition::BuiltinType(builtin) => impls_for_ty(&sema, builtin.ty(sema.db)),
-                    Definition::Function(f) => {
-                        let assoc = f.as_assoc_item(sema.db)?;
-                        let name = assoc.name(sema.db)?;
-                        let trait_ = assoc.container_or_implemented_trait(sema.db)?;
-                        impls_for_trait_item(&sema, trait_, name)
+    let navs = sema
+        .descend_into_macros_exact(original_token)
+        .iter()
+        .filter_map(|token| {
+            token
+                .parent()
+                .and_then(ast::NameLike::cast)
+                .and_then(|node| match &node {
+                    ast::NameLike::Name(name) => {
+                        NameClass::classify(&sema, name).and_then(|class| match class {
+                            NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
+                            NameClass::PatFieldShorthand { .. } => None,
+                        })
                     }
-                    Definition::Const(c) => {
-                        let assoc = c.as_assoc_item(sema.db)?;
-                        let name = assoc.name(sema.db)?;
-                        let trait_ = assoc.container_or_implemented_trait(sema.db)?;
-                        impls_for_trait_item(&sema, trait_, name)
-                    }
-                    _ => return None,
-                };
-                Some(navs)
-            })
-            .unwrap_or_default();
+                    ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
+                        .and_then(|class| match class {
+                            NameRefClass::Definition(def) => Some(def),
+                            NameRefClass::FieldShorthand { .. }
+                            | NameRefClass::ExternCrateShorthand { .. } => None,
+                        }),
+                    ast::NameLike::Lifetime(_) => None,
+                })
+                .and_then(|def| {
+                    let navs = match def {
+                        Definition::Trait(trait_) => impls_for_trait(&sema, trait_),
+                        Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)),
+                        Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
+                        Definition::BuiltinType(builtin) => {
+                            impls_for_ty(&sema, builtin.ty(sema.db))
+                        }
+                        Definition::Function(f) => {
+                            let assoc = f.as_assoc_item(sema.db)?;
+                            let name = assoc.name(sema.db)?;
+                            let trait_ = assoc.container_or_implemented_trait(sema.db)?;
+                            impls_for_trait_item(&sema, trait_, name)
+                        }
+                        Definition::Const(c) => {
+                            let assoc = c.as_assoc_item(sema.db)?;
+                            let name = assoc.name(sema.db)?;
+                            let trait_ = assoc.container_or_implemented_trait(sema.db)?;
+                            impls_for_trait_item(&sema, trait_, name)
+                        }
+                        _ => return None,
+                    };
+                    Some(navs)
+                })
+        })
+        .flatten()
+        .collect();
 
     Some(RangeInfo { range, info: navs })
 }
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index f75b8fb7d0..ca04b7bb5a 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -1,4 +1,4 @@
-use hir::{DescendPreference, GenericParam};
+use hir::GenericParam;
 use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
 
@@ -69,7 +69,7 @@ pub(crate) fn goto_type_definition(
     }
 
     let range = token.text_range();
-    sema.descend_into_macros(DescendPreference::None, token)
+    sema.descend_into_macros(token)
         .into_iter()
         .filter_map(|token| {
             let ty = sema
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index a88261df10..5348e855be 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -1,6 +1,6 @@
 use std::iter;
 
-use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics};
+use hir::{db, FilePosition, FileRange, HirFileId, InFile, Semantics};
 use ide_db::{
     defs::{Definition, IdentClass},
     helpers::pick_best_token,
@@ -542,7 +542,7 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange
 }
 
 fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
-    sema.descend_into_macros(DescendPreference::None, token)
+    sema.descend_into_macros_exact(token)
         .into_iter()
         .filter_map(|token| IdentClass::classify_token(sema, &token))
         .flat_map(IdentClass::definitions_no_ops)
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 6c646cf4ee..124db2985b 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -6,7 +6,7 @@ mod tests;
 use std::{iter, ops::Not};
 
 use either::Either;
-use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics};
+use hir::{db::DefDatabase, HasCrate, HasSource, LangItem, Semantics};
 use ide_db::{
     defs::{Definition, IdentClass, NameRefClass, OperatorClass},
     famous_defs::FamousDefs,
@@ -58,7 +58,7 @@ pub enum HoverDocFormat {
     PlainText,
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum HoverAction {
     Runnable(Runnable),
     Implementation(FilePosition),
@@ -97,7 +97,7 @@ pub struct HoverGotoTypeData {
 }
 
 /// Contains the results when hovering over an item
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
 pub struct HoverResult {
     pub markup: Markup,
     pub actions: Vec<HoverAction>,
@@ -119,7 +119,7 @@ pub(crate) fn hover(
     let edition =
         sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
     let mut res = if range.is_empty() {
-        hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config, edition)
+        hover_offset(sema, FilePosition { file_id, offset: range.start() }, file, config, edition)
     } else {
         hover_ranged(sema, frange, file, config, edition)
     }?;
@@ -131,7 +131,7 @@ pub(crate) fn hover(
 }
 
 #[allow(clippy::field_reassign_with_default)]
-fn hover_simple(
+fn hover_offset(
     sema: &Semantics<'_, RootDatabase>,
     FilePosition { file_id, offset }: FilePosition,
     file: SyntaxNode,
@@ -178,34 +178,43 @@ fn hover_simple(
         return Some(RangeInfo::new(range, res));
     }
 
-    let in_attr = original_token
-        .parent_ancestors()
-        .filter_map(ast::Item::cast)
-        .any(|item| sema.is_attr_macro_call(&item))
-        && !matches!(
-            original_token.parent().and_then(ast::TokenTree::cast),
-            Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
-        );
-
     // prefer descending the same token kind in attribute expansions, in normal macros text
     // equivalency is more important
-    let descended = sema.descend_into_macros(
-        if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText },
-        original_token.clone(),
-    );
-    let descended = || descended.iter();
+    let mut descended = sema.descend_into_macros(original_token.clone());
+
+    let kind = original_token.kind();
+    let text = original_token.text();
+    let ident_kind = kind.is_any_identifier();
+
+    descended.sort_by_cached_key(|tok| {
+        let tok_kind = tok.kind();
+
+        let exact_same_kind = tok_kind == kind;
+        let both_idents = exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
+        let same_text = tok.text() == text;
+        // anything that mapped into a token tree has likely no semantic information
+        let no_tt_parent = tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
+        !((both_idents as usize)
+            | ((exact_same_kind as usize) << 1)
+            | ((same_text as usize) << 2)
+            | ((no_tt_parent as usize) << 3))
+    });
 
-    let result = descended()
-        // try lint hover
-        .find_map(|token| {
+    let mut res = vec![];
+    for token in descended {
+        let is_same_kind = token.kind() == kind;
+        let lint_hover = (|| {
             // FIXME: Definition should include known lints and the like instead of having this special case here
             let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
-            render::try_for_lint(&attr, token)
-        })
-        // try definitions
-        .or_else(|| {
-            descended()
-                .filter_map(|token| {
+            render::try_for_lint(&attr, &token)
+        })();
+        if let Some(lint_hover) = lint_hover {
+            res.push(lint_hover);
+            continue;
+        }
+        let definitions = (|| {
+            Some(
+                'a: {
                     let node = token.parent()?;
 
                     // special case macro calls, we wanna render the invoked arm index
@@ -220,11 +229,11 @@ fn hover_simple(
                                 .and_then(ast::MacroCall::cast)
                             {
                                 if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
-                                    return Some(vec![(
+                                    break 'a vec![(
                                         Definition::Macro(macro_),
                                         sema.resolve_macro_call_arm(&macro_call),
                                         node,
-                                    )]);
+                                    )];
                                 }
                             }
                         }
@@ -233,88 +242,101 @@ fn hover_simple(
                     match IdentClass::classify_node(sema, &node)? {
                         // It's better for us to fall back to the keyword hover here,
                         // rendering poll is very confusing
-                        IdentClass::Operator(OperatorClass::Await(_)) => None,
+                        IdentClass::Operator(OperatorClass::Await(_)) => return None,
 
                         IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
                             decl,
                             ..
-                        }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
+                        }) => {
+                            vec![(Definition::ExternCrateDecl(decl), None, node)]
+                        }
 
-                        class => Some(
+                        class => {
                             multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
-                                .collect::<Vec<_>>(),
-                        ),
+                                .collect::<Vec<_>>()
+                        }
                     }
-                })
-                .flatten()
+                }
+                .into_iter()
                 .unique_by(|&(def, _, _)| def)
                 .map(|(def, macro_arm, node)| {
                     hover_for_definition(sema, file_id, def, &node, macro_arm, config, edition)
                 })
-                .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
-                    acc.actions.extend(actions);
-                    acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
-                    acc
-                })
-        })
-        // try keywords
-        .or_else(|| descended().find_map(|token| render::keyword(sema, config, token, edition)))
-        // try _ hovers
-        .or_else(|| descended().find_map(|token| render::underscore(sema, config, token, edition)))
-        // try rest pattern hover
-        .or_else(|| {
-            descended().find_map(|token| {
-                if token.kind() != DOT2 {
-                    return None;
-                }
+                .collect::<Vec<_>>(),
+            )
+        })();
+        if let Some(definitions) = definitions {
+            res.extend(definitions);
+            continue;
+        }
+        let keywords = || render::keyword(sema, config, &token, edition);
+        let underscore = || {
+            if !is_same_kind {
+                return None;
+            }
+            render::underscore(sema, config, &token, edition)
+        };
+        let rest_pat = || {
+            if !is_same_kind || token.kind() != DOT2 {
+                return None;
+            }
 
-                let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
-                let record_pat_field_list =
-                    rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
+            let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
+            let record_pat_field_list =
+                rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
 
-                let record_pat =
-                    record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
+            let record_pat =
+                record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
 
-                Some(render::struct_rest_pat(sema, config, &record_pat, edition))
-            })
-        })
-        // try () call hovers
-        .or_else(|| {
-            descended().find_map(|token| {
-                if token.kind() != T!['('] && token.kind() != T![')'] {
-                    return None;
-                }
-                let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
-                let call_expr = syntax::match_ast! {
-                    match arg_list {
-                        ast::CallExpr(expr) => expr.into(),
-                        ast::MethodCallExpr(expr) => expr.into(),
-                        _ => return None,
-                    }
-                };
-                render::type_info_of(sema, config, &Either::Left(call_expr), edition)
-            })
-        })
-        // try closure
-        .or_else(|| {
-            descended().find_map(|token| {
-                if token.kind() != T![|] {
-                    return None;
+            Some(render::struct_rest_pat(sema, config, &record_pat, edition))
+        };
+        let call = || {
+            if !is_same_kind || token.kind() != T!['('] && token.kind() != T![')'] {
+                return None;
+            }
+            let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
+            let call_expr = syntax::match_ast! {
+                match arg_list {
+                    ast::CallExpr(expr) => expr.into(),
+                    ast::MethodCallExpr(expr) => expr.into(),
+                    _ => return None,
                 }
-                let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
-                render::closure_expr(sema, config, c, edition)
-            })
-        })
-        // tokens
-        .or_else(|| {
+            };
+            render::type_info_of(sema, config, &Either::Left(call_expr), edition)
+        };
+        let closure = || {
+            if !is_same_kind || token.kind() != T![|] {
+                return None;
+            }
+            let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
+            render::closure_expr(sema, config, c, edition)
+        };
+        let literal = || {
             render::literal(sema, original_token.clone(), edition)
                 .map(|markup| HoverResult { markup, actions: vec![] })
-        });
+        };
+        if let Some(result) = keywords()
+            .or_else(underscore)
+            .or_else(rest_pat)
+            .or_else(call)
+            .or_else(closure)
+            .or_else(literal)
+        {
+            res.push(result)
+        }
+    }
 
-    result.map(|mut res: HoverResult| {
-        res.actions = dedupe_or_merge_hover_actions(res.actions);
-        RangeInfo::new(original_token.text_range(), res)
-    })
+    res.into_iter()
+        .unique()
+        .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
+            acc.actions.extend(actions);
+            acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
+            acc
+        })
+        .map(|mut res: HoverResult| {
+            res.actions = dedupe_or_merge_hover_actions(res.actions);
+            RangeInfo::new(original_token.text_range(), res)
+        })
 }
 
 fn hover_ranged(
diff --git a/crates/ide/src/markup.rs b/crates/ide/src/markup.rs
index 4a4e29fa33..750d125426 100644
--- a/crates/ide/src/markup.rs
+++ b/crates/ide/src/markup.rs
@@ -5,7 +5,7 @@
 //! what is used by LSP, so let's keep it simple.
 use std::fmt;
 
-#[derive(Default, Debug)]
+#[derive(Clone, Default, Debug, Hash, PartialEq, Eq)]
 pub struct Markup {
     text: String,
 }
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index 7b5fd651e3..4be1b57098 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -3,7 +3,7 @@
 
 use core::fmt;
 
-use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, DescendPreference, MacroKind, Semantics};
+use hir::{Adt, AsAssocItem, AssocItemContainer, Crate, MacroKind, Semantics};
 use ide_db::{
     base_db::{CrateOrigin, LangCrateOrigin},
     defs::{Definition, IdentClass},
@@ -154,7 +154,7 @@ pub(crate) fn moniker(
         });
     }
     let navs = sema
-        .descend_into_macros(DescendPreference::None, original_token.clone())
+        .descend_into_macros_exact(original_token.clone())
         .into_iter()
         .filter_map(|token| {
             IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops).map(|it| {
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 3e3d5ef6e6..55afcb59ba 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -9,7 +9,7 @@
 //! at the index that the match starts at and its tree parent is
 //! resolved to the search element definition, we get a reference.
 
-use hir::{DescendPreference, PathResolution, Semantics};
+use hir::{PathResolution, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     search::{ReferenceCategory, SearchScope, UsageSearchResult},
@@ -149,7 +149,7 @@ pub(crate) fn find_defs<'a>(
     }
 
     Some(
-        sema.descend_into_macros(DescendPreference::SameText, token)
+        sema.descend_into_macros_exact(token)
             .into_iter()
             .filter_map(|it| ast::NameLike::cast(it.parent()?))
             .filter_map(move |name_like| {
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index 9d3b8c6ebd..516f64959c 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -4,10 +4,7 @@
 use std::collections::BTreeSet;
 
 use either::Either;
-use hir::{
-    AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics,
-    Trait,
-};
+use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait};
 use ide_db::{
     active_parameter::{callable_for_node, generic_def_for_node},
     documentation::{Documentation, HasDocs},
@@ -82,7 +79,7 @@ pub(crate) fn signature_help(
         // if the cursor is sandwiched between two space tokens and the call is unclosed
         // this prevents us from leaving the CallExpression
         .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
-    let token = sema.descend_into_macros_single(DescendPreference::None, token);
+    let token = sema.descend_into_macros_single_exact(token);
     let edition =
         sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
 
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index e082fcdc76..927fdaa178 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -13,7 +13,9 @@ mod html;
 #[cfg(test)]
 mod tests;
 
-use hir::{DescendPreference, Name, Semantics};
+use std::ops::ControlFlow;
+
+use hir::{InRealFile, Name, Semantics};
 use ide_db::{FxHashMap, RootDatabase, SymbolKind};
 use span::EditionedFileId;
 use syntax::{
@@ -399,19 +401,55 @@ fn traverse(
             // Attempt to descend tokens into macro-calls.
             let res = match element {
                 NodeOrToken::Token(token) if token.kind() != COMMENT => {
-                    let token = if token.kind() == STRING {
-                        // for strings, try to prefer a string that has not been lost in a token
-                        // tree
-                        // FIXME: This should be done for everything, but check perf first
-                        sema.descend_into_macros(DescendPreference::SameKind, token)
-                            .into_iter()
-                            .max_by_key(|it| {
-                                it.parent().map_or(false, |it| it.kind() != TOKEN_TREE)
-                            })
-                            .unwrap()
-                    } else {
-                        sema.descend_into_macros_single(DescendPreference::SameKind, token)
-                    };
+                    let kind = token.kind();
+                    let text = token.text();
+                    let ident_kind = kind.is_any_identifier();
+
+                    let mut t = None;
+                    let mut r = 0;
+                    sema.descend_into_macros_breakable(
+                        InRealFile::new(file_id, token.clone()),
+                        |tok| {
+                            let tok = tok.value;
+                            let tok_kind = tok.kind();
+
+                            let exact_same_kind = tok_kind == kind;
+                            let both_idents =
+                                exact_same_kind || (tok_kind.is_any_identifier() && ident_kind);
+                            let same_text = tok.text() == text;
+                            // anything that mapped into a token tree has likely no semantic information
+                            let no_tt_parent =
+                                tok.parent().map_or(false, |it| it.kind() != TOKEN_TREE);
+                            let my_rank = (both_idents as usize)
+                                | ((exact_same_kind as usize) << 1)
+                                | ((same_text as usize) << 2)
+                                | ((no_tt_parent as usize) << 3);
+
+                            if my_rank > 0b1110 {
+                                // a rank of 0b1110 means that we have found a maximally interesting
+                                // token so stop early.
+                                t = Some(tok);
+                                return ControlFlow::Break(());
+                            }
+
+                            // r = r.max(my_rank);
+                            // t = Some(t.take_if(|_| r < my_rank).unwrap_or(tok));
+                            match &mut t {
+                                Some(prev) if r < my_rank => {
+                                    *prev = tok;
+                                    r = my_rank;
+                                }
+                                Some(_) => (),
+                                None => {
+                                    r = my_rank;
+                                    t = Some(tok)
+                                }
+                            }
+                            ControlFlow::Continue(())
+                        },
+                    );
+
+                    let token = t.unwrap_or(token);
                     match token.parent().and_then(ast::NameLike::cast) {
                         // Remap the token into the wrapping single token nodes
                         Some(parent) => match (token.kind(), parent.syntax().kind()) {


Test patch:
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
index 17411fefbd..196552020a 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -100,7 +100,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="brace">}</span><span class="semicolon">;</span>
 <span class="brace">}</span>
 
-<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="string_literal macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="comma macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
 
 <span class="keyword">struct</span> <span class="struct declaration">S</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>