about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-12-05 16:15:45 +0000
committerbors <bors@rust-lang.org>2023-12-05 16:15:45 +0000
commit05df6c52cac9f5ce37a543a0b81d3bd6a1ec9d6d (patch)
tree4214ec2f945c37963fa5f7f1cea9d52ebc2bece0
parentafc1ae1aa30f55395180ad770cc99c8fb1052338 (diff)
parent9b7ec5e31be75ed49b2cc7f341fd93ca6999a303 (diff)
Auto merge of #16027 - Veykril:implicit-format-args, r=Veykril
feat: Implicit format args support

Fixes https://github.com/rust-lang/rust-analyzer/issues/11260
Fixes https://github.com/rust-lang/rust-analyzer/issues/11296

![image](https://github.com/rust-lang/rust-analyzer/assets/3757771/14fe2caf-4ea3-40a5-8aa4-ff08ea0ccbde)
Too lazy to make a gif of this right now (would probably be good to show renaming)
-rw-r--r--crates/hir-def/src/body.rs12
-rw-r--r--crates/hir-def/src/body/lower.rs24
-rw-r--r--crates/hir-def/src/body/tests.rs6
-rw-r--r--crates/hir-def/src/hir/format_args.rs14
-rw-r--r--crates/hir-expand/src/lib.rs2
-rw-r--r--crates/hir/src/lib.rs4
-rw-r--r--crates/hir/src/semantics.rs263
-rw-r--r--crates/hir/src/source_analyzer.rs89
-rw-r--r--crates/ide-assists/src/handlers/bool_to_enum.rs2
-rw-r--r--crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs2
-rw-r--r--crates/ide-assists/src/handlers/extract_expressions_from_format_string.rs4
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs6
-rw-r--r--crates/ide-assists/src/handlers/inline_call.rs8
-rw-r--r--crates/ide-assists/src/handlers/inline_local_variable.rs4
-rw-r--r--crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs5
-rw-r--r--crates/ide-assists/src/handlers/unnecessary_async.rs6
-rw-r--r--crates/ide-db/src/helpers.rs4
-rw-r--r--crates/ide-db/src/rename.rs10
-rw-r--r--crates/ide-db/src/search.rs121
-rw-r--r--crates/ide/src/call_hierarchy.rs4
-rw-r--r--crates/ide/src/doc_links.rs8
-rw-r--r--crates/ide/src/expand_macro.rs8
-rw-r--r--crates/ide/src/extend_selection.rs14
-rw-r--r--crates/ide/src/goto_declaration.rs4
-rw-r--r--crates/ide/src/goto_definition.rs40
-rw-r--r--crates/ide/src/goto_implementation.rs4
-rw-r--r--crates/ide/src/goto_type_definition.rs84
-rw-r--r--crates/ide/src/highlight_related.rs41
-rw-r--r--crates/ide/src/hover.rs28
-rw-r--r--crates/ide/src/hover/tests.rs85
-rw-r--r--crates/ide/src/moniker.rs4
-rw-r--r--crates/ide/src/references.rs52
-rw-r--r--crates/ide/src/rename.rs91
-rw-r--r--crates/ide/src/runnables.rs4
-rw-r--r--crates/ide/src/signature_help.rs7
-rw-r--r--crates/ide/src/syntax_highlighting.rs29
-rw-r--r--crates/ide/src/syntax_highlighting/format.rs20
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_macros.html10
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html48
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs41
-rw-r--r--crates/mbe/src/token_map.rs1
-rw-r--r--crates/syntax/src/ast/token_ext.rs6
-rw-r--r--crates/syntax/src/token_text.rs2
-rw-r--r--crates/test-utils/src/minicore.rs29
45 files changed, 921 insertions, 331 deletions
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index 1942c60c075..db28c6731ec 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -95,6 +95,8 @@ pub struct BodySourceMap {
     field_map_back: FxHashMap<ExprId, FieldSource>,
     pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
 
+    format_args_template_map: FxHashMap<ExprId, Vec<(syntax::TextRange, Name)>>,
+
     expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
 
     /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
@@ -387,6 +389,14 @@ impl BodySourceMap {
         self.expr_map.get(&src).copied()
     }
 
+    pub fn implicit_format_args(
+        &self,
+        node: InFile<&ast::FormatArgsExpr>,
+    ) -> Option<&[(syntax::TextRange, Name)]> {
+        let src = node.map(AstPtr::new).map(AstPtr::upcast::<ast::Expr>);
+        self.format_args_template_map.get(self.expr_map.get(&src)?).map(std::ops::Deref::deref)
+    }
+
     /// Get a reference to the body source map's diagnostics.
     pub fn diagnostics(&self) -> &[BodyDiagnostic] {
         &self.diagnostics
@@ -403,8 +413,10 @@ impl BodySourceMap {
             field_map_back,
             pat_field_map_back,
             expansions,
+            format_args_template_map,
             diagnostics,
         } = self;
+        format_args_template_map.shrink_to_fit();
         expr_map.shrink_to_fit();
         expr_map_back.shrink_to_fit();
         pat_map.shrink_to_fit();
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 0466068ec81..c22bd6e2e57 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -1597,12 +1597,20 @@ impl ExprCollector<'_> {
         });
         let template = f.template();
         let fmt_snippet = template.as_ref().map(ToString::to_string);
+        let mut mappings = vec![];
         let fmt = match template.and_then(|it| self.expand_macros_to_string(it)) {
-            Some((s, is_direct_literal)) => {
-                format_args::parse(&s, fmt_snippet, args, is_direct_literal, |name| {
-                    self.alloc_expr_desugared(Expr::Path(Path::from(name)))
-                })
-            }
+            Some((s, is_direct_literal)) => format_args::parse(
+                &s,
+                fmt_snippet,
+                args,
+                is_direct_literal,
+                |name| self.alloc_expr_desugared(Expr::Path(Path::from(name))),
+                |name, span| {
+                    if let Some(span) = span {
+                        mappings.push((span, name.clone()))
+                    }
+                },
+            ),
             None => FormatArgs { template: Default::default(), arguments: args.finish() },
         };
 
@@ -1746,14 +1754,16 @@ impl ExprCollector<'_> {
             tail: Some(unsafe_arg_new),
         });
 
-        self.alloc_expr(
+        let idx = self.alloc_expr(
             Expr::Call {
                 callee: new_v1_formatted,
                 args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
                 is_assignee_expr: false,
             },
             syntax_ptr,
-        )
+        );
+        self.source_map.format_args_template_map.insert(idx, mappings);
+        idx
     }
 
     /// Generate a hir expression for a format_args placeholder specification.
diff --git a/crates/hir-def/src/body/tests.rs b/crates/hir-def/src/body/tests.rs
index 5517abb1ab7..2b432dfbb92 100644
--- a/crates/hir-def/src/body/tests.rs
+++ b/crates/hir-def/src/body/tests.rs
@@ -160,7 +160,7 @@ fn main() {
             let count = 10;
             builtin#lang(Arguments::new_v1_formatted)(
                 &[
-                    "\"hello ", " ", " friends, we ", " ", "", "\"",
+                    "hello ", " ", " friends, we ", " ", "",
                 ],
                 &[
                     builtin#lang(Argument::new_display)(
@@ -261,7 +261,7 @@ impl SsrError {
             _ = $crate::error::SsrError::new(
                 builtin#lang(Arguments::new_v1_formatted)(
                     &[
-                        "\"Failed to resolve path `", "`\"",
+                        "Failed to resolve path `", "`",
                     ],
                     &[
                         builtin#lang(Argument::new_display)(
@@ -320,7 +320,7 @@ fn f() {
             $crate::panicking::panic_fmt(
                 builtin#lang(Arguments::new_v1_formatted)(
                     &[
-                        "\"cc\"",
+                        "cc",
                     ],
                     &[],
                     &[],
diff --git a/crates/hir-def/src/hir/format_args.rs b/crates/hir-def/src/hir/format_args.rs
index 46d24bd4a61..068abb27a25 100644
--- a/crates/hir-def/src/hir/format_args.rs
+++ b/crates/hir-def/src/hir/format_args.rs
@@ -5,7 +5,7 @@ use hir_expand::name::Name;
 use rustc_dependencies::parse_format as parse;
 use syntax::{
     ast::{self, IsString},
-    AstToken, SmolStr, TextRange,
+    SmolStr, TextRange, TextSize,
 };
 
 use crate::hir::ExprId;
@@ -170,15 +170,18 @@ pub(crate) fn parse(
     mut args: FormatArgumentsCollector,
     is_direct_literal: bool,
     mut synth: impl FnMut(Name) -> ExprId,
+    mut record_usage: impl FnMut(Name, Option<TextRange>),
 ) -> FormatArgs {
-    let text = s.text();
+    let text = s.text_without_quotes();
     let str_style = match s.quote_offsets() {
         Some(offsets) => {
             let raw = u32::from(offsets.quotes.0.len()) - 1;
-            (raw != 0).then_some(raw as usize)
+            // subtract 1 for the `r` prefix
+            (raw != 0).then(|| raw as usize - 1)
         }
         None => None,
     };
+
     let mut parser =
         parse::Parser::new(text, str_style, fmt_snippet, false, parse::ParseMode::Format);
 
@@ -199,6 +202,7 @@ pub(crate) fn parse(
     let to_span = |inner_span: parse::InnerSpan| {
         is_source_literal.then(|| {
             TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
+                - TextSize::from(str_style.map(|it| it + 1).unwrap_or(0) as u32 + 1)
         })
     };
 
@@ -230,9 +234,10 @@ pub(crate) fn parse(
                     Err(index)
                 }
             }
-            ArgRef::Name(name, _span) => {
+            ArgRef::Name(name, span) => {
                 let name = Name::new_text_dont_use(SmolStr::new(name));
                 if let Some((index, _)) = args.by_name(&name) {
+                    record_usage(name, span);
                     // Name found in `args`, so we resolve it to its index.
                     if index < args.explicit_args().len() {
                         // Mark it as used, if it was an explicit argument.
@@ -246,6 +251,7 @@ pub(crate) fn parse(
                         // disabled (see RFC #2795)
                         // FIXME: Diagnose
                     }
+                    record_usage(name.clone(), span);
                     Ok(args.add(FormatArgument {
                         kind: FormatArgumentKind::Captured(name.clone()),
                         // FIXME: This is problematic, we might want to synthesize a dummy
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index 602babcc992..71c98b2770a 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -629,8 +629,6 @@ impl ExpansionInfo {
     pub fn map_range_down<'a>(
         &'a self,
         span: SpanData,
-        // FIXME: use this for range mapping, so that we can resolve inline format args
-        _relative_token_offset: Option<TextSize>,
     ) -> Option<impl Iterator<Item = InMacroFile<SyntaxToken>> + 'a> {
         let tokens = self
             .exp_map
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 22e14b61812..53e60c5862a 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -92,7 +92,9 @@ pub use crate::{
     attrs::{resolve_doc_path_on, HasAttrs},
     diagnostics::*,
     has_source::HasSource,
-    semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
+    semantics::{
+        DescendPreference, PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits,
+    },
 };
 
 // Be careful with these re-exports.
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index ed3d3f1a3b6..ac70c27785c 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -2,7 +2,11 @@
 
 mod source_to_def;
 
-use std::{cell::RefCell, fmt, iter, mem, ops};
+use std::{
+    cell::RefCell,
+    fmt, iter, mem,
+    ops::{self, ControlFlow},
+};
 
 use base_db::{FileId, FileRange};
 use either::Either;
@@ -25,8 +29,9 @@ use smallvec::{smallvec, SmallVec};
 use stdx::TupleExt;
 use syntax::{
     algo::skip_trivia_token,
-    ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody},
-    match_ast, AstNode, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
+    ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody, IsString as _},
+    match_ast, AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken,
+    TextRange, TextSize,
 };
 
 use crate::{
@@ -39,7 +44,13 @@ use crate::{
     TypeAlias, TypeParam, VariantDef,
 };
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum DescendPreference {
+    SameText,
+    SameKind,
+    None,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum PathResolution {
     /// An item
     Def(ModuleDef),
@@ -392,11 +403,72 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    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
+    }
+
+    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.clone())
+                    .into_iter()
+                    .find_map(|token| {
+                        self.resolve_offset_in_format_args(
+                            ast::String::cast(token)?,
+                            offset - quote.end(),
+                        )
+                    })
+                    .map(|(range, res)| (range + quote.end(), res));
+            }
+        }
+        None
+    }
+
+    fn resolve_offset_in_format_args(
+        &self,
+        string: ast::String,
+        offset: TextSize,
+    ) -> Option<(TextRange, Option<PathResolution>)> {
+        debug_assert!(offset <= string.syntax().text_range().len());
+        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);
+        source_analyzer.resolve_offset_in_format_args(self.db, format_args.as_ref(), offset)
+    }
+
     /// Maps a node down by mapping its first and last token down.
     pub fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
         // This might not be the correct way to do this, but it works for now
         let mut res = smallvec![];
         let tokens = (|| {
+            // FIXME: the trivia skipping should not be necessary
             let first = skip_trivia_token(node.syntax().first_token()?, Direction::Next)?;
             let last = skip_trivia_token(node.syntax().last_token()?, Direction::Prev)?;
             Some((first, last))
@@ -407,24 +479,28 @@ impl<'db> SemanticsImpl<'db> {
         };
 
         if first == last {
-            self.descend_into_macros_impl(first, 0.into(), &mut |InFile { value, .. }| {
-                if let Some(node) = value.parent_ancestors().find_map(N::cast) {
+            // 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)
                 }
-                false
+                ControlFlow::Continue(())
             });
         } 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, 0.into(), &mut |token| {
+            self.descend_into_macros_impl(first, &mut |token| {
                 scratch.push(token);
-                false
+                ControlFlow::Continue(())
             });
 
             let mut scratch = scratch.into_iter();
             self.descend_into_macros_impl(
                 last,
-                0.into(),
                 &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 {
@@ -441,7 +517,7 @@ impl<'db> SemanticsImpl<'db> {
                             }
                         }
                     }
-                    false
+                    ControlFlow::Continue(())
                 },
             );
         }
@@ -453,32 +529,42 @@ impl<'db> SemanticsImpl<'db> {
     /// be considered for the mapping in case of inline format args.
     pub fn descend_into_macros(
         &self,
+        mode: DescendPreference,
         token: SyntaxToken,
-        offset: TextSize,
-    ) -> SmallVec<[SyntaxToken; 1]> {
-        let mut res = smallvec![];
-        self.descend_into_macros_impl(token, offset, &mut |InFile { value, .. }| {
-            res.push(value);
-            false
-        });
-        res
-    }
-
-    /// Descend the token into macrocalls to all its mapped counterparts that have the same text as the input token.
-    ///
-    /// Returns the original non descended token if none of the mapped counterparts have the same text.
-    pub fn descend_into_macros_with_same_text(
-        &self,
-        token: SyntaxToken,
-        offset: TextSize,
     ) -> SmallVec<[SyntaxToken; 1]> {
-        let text = token.text();
+        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 = smallvec![];
-        self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| {
-            if value.text() == text {
+        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);
             }
-            false
+            ControlFlow::Continue(())
         });
         if res.is_empty() {
             res.push(token);
@@ -486,44 +572,46 @@ impl<'db> SemanticsImpl<'db> {
         res
     }
 
-    pub fn descend_into_macros_with_kind_preference(
+    pub fn descend_into_macros_single(
         &self,
+        mode: DescendPreference,
         token: SyntaxToken,
-        offset: TextSize,
     ) -> 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) => {
-                    node.parent().map_or(kind, |it| it.kind())
-                }
+                kind @ (SyntaxKind::NAME | SyntaxKind::NAME_REF) => kind,
                 _ => token.kind(),
             },
             None => token.kind(),
         };
-        let preferred_kind = fetch_kind(&token);
-        let mut res = None;
-        self.descend_into_macros_impl(token.clone(), offset, &mut |InFile { value, .. }| {
-            if fetch_kind(&value) == preferred_kind {
-                res = Some(value);
-                true
-            } else {
-                if let None = res {
-                    res = Some(value)
-                }
-                false
-            }
-        });
-        res.unwrap_or(token)
-    }
-
-    /// Descend the token into its macro call if it is part of one, returning the token in the
-    /// expansion that it is associated with. If `offset` points into the token's range, it will
-    /// be considered for the mapping in case of inline format args.
-    pub fn descend_into_macros_single(&self, token: SyntaxToken, offset: TextSize) -> SyntaxToken {
+        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, offset, &mut |InFile { value, .. }| {
+        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;
-            true
+            if is_a_match {
+                ControlFlow::Break(())
+            } else {
+                ControlFlow::Continue(())
+            }
         });
         res
     }
@@ -532,10 +620,7 @@ impl<'db> SemanticsImpl<'db> {
     fn descend_into_macros_impl(
         &self,
         token: SyntaxToken,
-        // FIXME: We might want this to be Option<TextSize> to be able to opt out of subrange
-        // mapping, specifically for node downmapping
-        _offset: TextSize,
-        f: &mut dyn FnMut(InFile<SyntaxToken>) -> bool,
+        f: &mut dyn FnMut(InFile<SyntaxToken>) -> ControlFlow<()>,
     ) {
         // FIXME: Clean this up
         let _p = profile::span("descend_into_macros");
@@ -560,25 +645,24 @@ impl<'db> SemanticsImpl<'db> {
         let def_map = sa.resolver.def_map();
         let mut stack: SmallVec<[_; 4]> = smallvec![InFile::new(sa.file_id, token)];
 
-        let mut process_expansion_for_token =
-            |stack: &mut SmallVec<_>, macro_file, _token: InFile<&_>| {
-                let expansion_info = cache
-                    .entry(macro_file)
-                    .or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
+        let mut process_expansion_for_token = |stack: &mut SmallVec<_>, macro_file| {
+            let expansion_info = cache
+                .entry(macro_file)
+                .or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
 
-                {
-                    let InFile { file_id, value } = expansion_info.expanded();
-                    self.cache(value, file_id);
-                }
+            {
+                let InFile { file_id, value } = expansion_info.expanded();
+                self.cache(value, file_id);
+            }
 
-                let mapped_tokens = expansion_info.map_range_down(span, None)?;
-                let len = stack.len();
+            let mapped_tokens = expansion_info.map_range_down(span)?;
+            let len = stack.len();
 
-                // requeue the tokens we got from mapping our current token down
-                stack.extend(mapped_tokens.map(Into::into));
-                // if the length changed we have found a mapping for the token
-                (stack.len() != len).then_some(())
-            };
+            // requeue the tokens we got from mapping our current token down
+            stack.extend(mapped_tokens.map(Into::into));
+            // if the length changed we have found a mapping for the token
+            (stack.len() != len).then_some(())
+        };
 
         // Remap the next token in the queue into a macro call its in, if it is not being remapped
         // either due to not being in a macro-call or because its unused push it into the result vec,
@@ -598,7 +682,7 @@ impl<'db> SemanticsImpl<'db> {
                 });
                 if let Some(call_id) = containing_attribute_macro_call {
                     let file_id = call_id.as_macro_file();
-                    return process_expansion_for_token(&mut stack, file_id, token.as_ref());
+                    return process_expansion_for_token(&mut stack, file_id);
                 }
 
                 // Then check for token trees, that means we are either in a function-like macro or
@@ -624,7 +708,7 @@ impl<'db> SemanticsImpl<'db> {
                             it
                         }
                     };
-                    process_expansion_for_token(&mut stack, file_id, token.as_ref())
+                    process_expansion_for_token(&mut stack, file_id)
                 } else if let Some(meta) = ast::Meta::cast(parent) {
                     // attribute we failed expansion for earlier, this might be a derive invocation
                     // or derive helper attribute
@@ -646,11 +730,7 @@ impl<'db> SemanticsImpl<'db> {
                             Some(call_id) => {
                                 // resolved to a derive
                                 let file_id = call_id.as_macro_file();
-                                return process_expansion_for_token(
-                                    &mut stack,
-                                    file_id,
-                                    token.as_ref(),
-                                );
+                                return process_expansion_for_token(&mut stack, file_id);
                             }
                             None => Some(adt),
                         }
@@ -682,11 +762,8 @@ impl<'db> SemanticsImpl<'db> {
                         def_map.derive_helpers_in_scope(InFile::new(token.file_id, id))?;
                     let mut res = None;
                     for (.., derive) in helpers.iter().filter(|(helper, ..)| *helper == attr_name) {
-                        res = res.or(process_expansion_for_token(
-                            &mut stack,
-                            derive.as_macro_file(),
-                            token.as_ref(),
-                        ));
+                        res =
+                            res.or(process_expansion_for_token(&mut stack, derive.as_macro_file()));
                     }
                     res
                 } else {
@@ -695,7 +772,7 @@ impl<'db> SemanticsImpl<'db> {
             })()
             .is_none();
 
-            if was_not_remapped && f(token) {
+            if was_not_remapped && f(token).is_break() {
                 break;
             }
         }
@@ -711,7 +788,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(token, offset))
+            .map(move |token| self.descend_into_macros(DescendPreference::None, token))
             .map(|descendants| {
                 descendants.into_iter().map(move |it| self.token_ancestors_with_macros(it))
             })
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 8b114836896..8afa7e06594 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -820,6 +820,52 @@ impl SourceAnalyzer {
         false
     }
 
+    pub(crate) fn resolve_offset_in_format_args(
+        &self,
+        db: &dyn HirDatabase,
+        format_args: InFile<&ast::FormatArgsExpr>,
+        offset: TextSize,
+    ) -> Option<(TextRange, Option<PathResolution>)> {
+        let implicits = self.body_source_map()?.implicit_format_args(format_args)?;
+        implicits.iter().find(|(range, _)| range.contains_inclusive(offset)).map(|(range, name)| {
+            (
+                *range,
+                resolve_hir_value_path(
+                    db,
+                    &self.resolver,
+                    self.resolver.body_owner(),
+                    &Path::from_known_path_with_no_generic(ModPath::from_segments(
+                        PathKind::Plain,
+                        Some(name.clone()),
+                    )),
+                ),
+            )
+        })
+    }
+
+    pub(crate) fn as_format_args_parts<'a>(
+        &'a self,
+        db: &'a dyn HirDatabase,
+        format_args: InFile<&ast::FormatArgsExpr>,
+    ) -> Option<impl Iterator<Item = (TextRange, Option<PathResolution>)> + 'a> {
+        Some(self.body_source_map()?.implicit_format_args(format_args)?.iter().map(
+            move |(range, name)| {
+                (
+                    *range,
+                    resolve_hir_value_path(
+                        db,
+                        &self.resolver,
+                        self.resolver.body_owner(),
+                        &Path::from_known_path_with_no_generic(ModPath::from_segments(
+                            PathKind::Plain,
+                            Some(name.clone()),
+                        )),
+                    ),
+                )
+            },
+        ))
+    }
+
     fn resolve_impl_method_or_trait_def(
         &self,
         db: &dyn HirDatabase,
@@ -1038,24 +1084,7 @@ fn resolve_hir_path_(
     };
 
     let body_owner = resolver.body_owner();
-    let values = || {
-        resolver.resolve_path_in_value_ns_fully(db.upcast(), path).and_then(|val| {
-            let res = match val {
-                ValueNs::LocalBinding(binding_id) => {
-                    let var = Local { parent: body_owner?, binding_id };
-                    PathResolution::Local(var)
-                }
-                ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
-                ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
-                ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
-                ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
-                ValueNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
-                ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
-                ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()),
-            };
-            Some(res)
-        })
-    };
+    let values = || resolve_hir_value_path(db, resolver, body_owner, path);
 
     let items = || {
         resolver
@@ -1075,6 +1104,30 @@ fn resolve_hir_path_(
         .or_else(macros)
 }
 
+fn resolve_hir_value_path(
+    db: &dyn HirDatabase,
+    resolver: &Resolver,
+    body_owner: Option<DefWithBodyId>,
+    path: &Path,
+) -> Option<PathResolution> {
+    resolver.resolve_path_in_value_ns_fully(db.upcast(), path).and_then(|val| {
+        let res = match val {
+            ValueNs::LocalBinding(binding_id) => {
+                let var = Local { parent: body_owner?, binding_id };
+                PathResolution::Local(var)
+            }
+            ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
+            ValueNs::ConstId(it) => PathResolution::Def(Const::from(it).into()),
+            ValueNs::StaticId(it) => PathResolution::Def(Static::from(it).into()),
+            ValueNs::StructId(it) => PathResolution::Def(Struct::from(it).into()),
+            ValueNs::EnumVariantId(it) => PathResolution::Def(Variant::from(it).into()),
+            ValueNs::ImplSelf(impl_id) => PathResolution::SelfType(impl_id.into()),
+            ValueNs::GenericParam(id) => PathResolution::ConstParam(id.into()),
+        };
+        Some(res)
+    })
+}
+
 /// Resolves a path where we know it is a qualifier of another path.
 ///
 /// For example, if we have:
diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs
index 11facc5bee2..0f2d1057c0a 100644
--- a/crates/ide-assists/src/handlers/bool_to_enum.rs
+++ b/crates/ide-assists/src/handlers/bool_to_enum.rs
@@ -328,6 +328,7 @@ fn augment_references_with_imports(
     references
         .iter()
         .filter_map(|FileReference { range, name, .. }| {
+            let name = name.clone().into_name_like()?;
             ctx.sema.scope(name.syntax()).map(|scope| (*range, name, scope.module()))
         })
         .map(|(range, name, ref_module)| {
@@ -455,6 +456,7 @@ fn add_enum_def(
         .iter()
         .flat_map(|(_, refs)| refs)
         .filter_map(|FileReference { name, .. }| {
+            let name = name.clone().into_name_like()?;
             ctx.sema.scope(name.syntax()).map(|scope| scope.module())
         })
         .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
index 1f3caa7db33..79b46d66121 100644
--- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
+++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs
@@ -186,6 +186,7 @@ fn augment_references_with_imports(
     references
         .iter()
         .filter_map(|FileReference { name, .. }| {
+            let name = name.clone().into_name_like()?;
             ctx.sema.scope(name.syntax()).map(|scope| (name, scope.module()))
         })
         .map(|(name, ref_module)| {
@@ -238,6 +239,7 @@ fn add_tuple_struct_def(
         .iter()
         .flat_map(|(_, refs)| refs)
         .filter_map(|FileReference { name, .. }| {
+            let name = name.clone().into_name_like()?;
             ctx.sema.scope(name.syntax()).map(|scope| scope.module())
         })
         .any(|module| module.nearest_non_block_module(ctx.db()) != *target_module);
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 31a1ff496e1..9d72d3af096 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,4 +1,5 @@
 use crate::{AssistContext, Assists};
+use hir::DescendPreference;
 use ide_db::{
     assists::{AssistId, AssistKind},
     syntax_helpers::{
@@ -35,7 +36,8 @@ pub(crate) fn extract_expressions_from_format_string(
     let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
 
     let expanded_t = ast::String::cast(
-        ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone(), 0.into()),
+        ctx.sema
+            .descend_into_macros_single(DescendPreference::SameKind, fmt_string.syntax().clone()),
     )?;
     if !is_format_string(&expanded_t) {
         return None;
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index 6b48d158815..d4a12307790 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;
 use ast::make;
 use either::Either;
 use hir::{
-    HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
-    TypeInfo, TypeParam,
+    DescendPreference, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef,
+    PathResolution, Semantics, TypeInfo, TypeParam,
 };
 use ide_db::{
     defs::{Definition, NameRefClass},
@@ -751,7 +751,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(t, 0.into()))
+                        .flat_map(|t| sema.descend_into_macros(DescendPreference::None, t))
                         .for_each(|t| add_name_if_local(t.parent().and_then(ast::NameRef::cast)));
                 }
             }
diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs
index f8c75bdb0de..5b9cc5f66cd 100644
--- a/crates/ide-assists/src/handlers/inline_call.rs
+++ b/crates/ide-assists/src/handlers/inline_call.rs
@@ -8,7 +8,7 @@ use ide_db::{
     defs::Definition,
     imports::insert_use::remove_path_if_in_use_stmt,
     path_transform::PathTransform,
-    search::{FileReference, SearchScope},
+    search::{FileReference, FileReferenceNode, SearchScope},
     source_change::SourceChangeBuilder,
     syntax_helpers::{insert_whitespace_into_node::insert_ws_into, node_ext::expr_as_name_ref},
     RootDatabase,
@@ -148,7 +148,7 @@ pub(super) fn split_refs_and_uses<T: ast::AstNode>(
 ) -> (Vec<T>, Vec<ast::Path>) {
     iter.into_iter()
         .filter_map(|file_ref| match file_ref.name {
-            ast::NameLike::NameRef(name_ref) => Some(name_ref),
+            FileReferenceNode::NameRef(name_ref) => Some(name_ref),
             _ => None,
         })
         .filter_map(|name_ref| match name_ref.syntax().ancestors().find_map(ast::UseTree::cast) {
@@ -346,7 +346,7 @@ fn inline(
             match param.as_local(sema.db) {
                 Some(l) => usages_for_locals(l)
                     .map(|FileReference { name, range, .. }| match name {
-                        ast::NameLike::NameRef(_) => body
+                        FileReferenceNode::NameRef(_) => body
                             .syntax()
                             .covering_element(range)
                             .ancestors()
@@ -372,7 +372,7 @@ fn inline(
         if let Some(self_local) = params[0].2.as_local(sema.db) {
             usages_for_locals(self_local)
                 .filter_map(|FileReference { name, range, .. }| match name {
-                    ast::NameLike::NameRef(_) => Some(body.syntax().covering_element(range)),
+                    FileReferenceNode::NameRef(_) => Some(body.syntax().covering_element(range)),
                     _ => None,
                 })
                 .for_each(|usage| {
diff --git a/crates/ide-assists/src/handlers/inline_local_variable.rs b/crates/ide-assists/src/handlers/inline_local_variable.rs
index 49dcde75d2b..5d8ba43ec84 100644
--- a/crates/ide-assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide-assists/src/handlers/inline_local_variable.rs
@@ -2,7 +2,7 @@ use hir::{PathResolution, Semantics};
 use ide_db::{
     base_db::FileId,
     defs::Definition,
-    search::{FileReference, UsageSearchResult},
+    search::{FileReference, FileReferenceNode, UsageSearchResult},
     RootDatabase,
 };
 use syntax::{
@@ -63,7 +63,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
     let wrap_in_parens = references
         .into_iter()
         .filter_map(|FileReference { range, name, .. }| match name {
-            ast::NameLike::NameRef(name) => Some((range, name)),
+            FileReferenceNode::NameRef(name) => Some((range, name)),
             _ => None,
         })
         .map(|(range, name_ref)| {
diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index c7c0be4c7d4..e61ce481727 100644
--- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -59,7 +59,10 @@ pub(crate) fn replace_named_generic_with_impl(
     let mut path_types_to_replace = Vec::new();
     for (_a, refs) in usage_refs.iter() {
         for usage_ref in refs {
-            let param_node = find_path_type(&ctx.sema, &type_param_name, &usage_ref.name)?;
+            let Some(name_like) = usage_ref.name.clone().into_name_like() else {
+                continue;
+            };
+            let param_node = find_path_type(&ctx.sema, &type_param_name, &name_like)?;
             path_types_to_replace.push(param_node);
         }
     }
diff --git a/crates/ide-assists/src/handlers/unnecessary_async.rs b/crates/ide-assists/src/handlers/unnecessary_async.rs
index 7f612c2a142..1cfa291a29d 100644
--- a/crates/ide-assists/src/handlers/unnecessary_async.rs
+++ b/crates/ide-assists/src/handlers/unnecessary_async.rs
@@ -2,11 +2,11 @@ use ide_db::{
     assists::{AssistId, AssistKind},
     base_db::FileId,
     defs::Definition,
-    search::FileReference,
+    search::{FileReference, FileReferenceNode},
     syntax_helpers::node_ext::full_path_of_name_ref,
 };
 use syntax::{
-    ast::{self, NameLike, NameRef},
+    ast::{self, NameRef},
     AstNode, SyntaxKind, TextRange,
 };
 
@@ -76,7 +76,7 @@ pub(crate) fn unnecessary_async(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
                 for await_expr in find_all_references(ctx, &Definition::Function(fn_def))
                     // Keep only references that correspond NameRefs.
                     .filter_map(|(_, reference)| match reference.name {
-                        NameLike::NameRef(nameref) => Some(nameref),
+                        FileReferenceNode::NameRef(nameref) => Some(nameref),
                         _ => None,
                     })
                     // Keep only references that correspond to await expressions
diff --git a/crates/ide-db/src/helpers.rs b/crates/ide-db/src/helpers.rs
index 330af442f75..9363bdfa14b 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::{FileId, SourceDatabaseExt};
-use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics};
+use hir::{Crate, DescendPreference, ItemInNs, ModuleDef, Name, Semantics};
 use syntax::{
     ast::{self, make},
     AstToken, SyntaxKind, SyntaxToken, TokenAtOffset,
@@ -117,7 +117,7 @@ pub fn get_definition(
     sema: &Semantics<'_, RootDatabase>,
     token: SyntaxToken,
 ) -> Option<Definition> {
-    for token in sema.descend_into_macros(token, 0.into()) {
+    for token in sema.descend_into_macros(DescendPreference::None, 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/rename.rs b/crates/ide-db/src/rename.rs
index 676d286b8dc..d2b6a732689 100644
--- a/crates/ide-db/src/rename.rs
+++ b/crates/ide-db/src/rename.rs
@@ -34,7 +34,7 @@ use text_edit::{TextEdit, TextEditBuilder};
 
 use crate::{
     defs::Definition,
-    search::FileReference,
+    search::{FileReference, FileReferenceNode},
     source_change::{FileSystemEdit, SourceChange},
     syntax_helpers::node_ext::expr_as_name_ref,
     traits::convert_to_def_in_trait,
@@ -361,7 +361,7 @@ pub fn source_edit_from_references(
     // macros can cause multiple refs to occur for the same text range, so keep track of what we have edited so far
     let mut edited_ranges = Vec::new();
     for &FileReference { range, ref name, .. } in references {
-        let name_range = name.syntax().text_range();
+        let name_range = name.text_range();
         if name_range.len() != range.len() {
             // This usage comes from a different token kind that was downmapped to a NameLike in a macro
             // Renaming this will most likely break things syntax-wise
@@ -371,17 +371,17 @@ pub fn source_edit_from_references(
             // if the ranges differ then the node is inside a macro call, we can't really attempt
             // to make special rewrites like shorthand syntax and such, so just rename the node in
             // the macro input
-            ast::NameLike::NameRef(name_ref) if name_range == range => {
+            FileReferenceNode::NameRef(name_ref) if name_range == range => {
                 source_edit_from_name_ref(&mut edit, name_ref, new_name, def)
             }
-            ast::NameLike::Name(name) if name_range == range => {
+            FileReferenceNode::Name(name) if name_range == range => {
                 source_edit_from_name(&mut edit, name, new_name)
             }
             _ => false,
         };
         if !has_emitted_edit && !edited_ranges.contains(&range.start()) {
             let (range, new_name) = match name {
-                ast::NameLike::Lifetime(_) => (
+                FileReferenceNode::Lifetime(_) => (
                     TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
                     new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
                 ),
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index 2ce036c044a..dbef3602682 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -8,14 +8,14 @@ use std::mem;
 
 use base_db::{salsa::Database, FileId, FileRange, SourceDatabase, SourceDatabaseExt};
 use hir::{
-    AsAssocItem, DefWithBody, HasAttrs, HasSource, HirFileIdExt, InFile, InRealFile, ModuleSource,
-    Semantics, Visibility,
+    AsAssocItem, DefWithBody, DescendPreference, HasAttrs, HasSource, HirFileIdExt, InFile,
+    InRealFile, ModuleSource, PathResolution, Semantics, Visibility,
 };
 use memchr::memmem::Finder;
 use nohash_hasher::IntMap;
 use once_cell::unsync::Lazy;
 use parser::SyntaxKind;
-use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
+use syntax::{ast, match_ast, AstNode, AstToken, SyntaxElement, TextRange, TextSize};
 use triomphe::Arc;
 
 use crate::{
@@ -63,10 +63,67 @@ pub struct FileReference {
     /// The range of the reference in the original file
     pub range: TextRange,
     /// The node of the reference in the (macro-)file
-    pub name: ast::NameLike,
+    pub name: FileReferenceNode,
     pub category: Option<ReferenceCategory>,
 }
 
+#[derive(Debug, Clone)]
+pub enum FileReferenceNode {
+    Name(ast::Name),
+    NameRef(ast::NameRef),
+    Lifetime(ast::Lifetime),
+    FormatStringEntry(ast::String, TextRange),
+}
+
+impl FileReferenceNode {
+    pub fn text_range(&self) -> TextRange {
+        match self {
+            FileReferenceNode::Name(it) => it.syntax().text_range(),
+            FileReferenceNode::NameRef(it) => it.syntax().text_range(),
+            FileReferenceNode::Lifetime(it) => it.syntax().text_range(),
+            FileReferenceNode::FormatStringEntry(_, range) => *range,
+        }
+    }
+    pub fn syntax(&self) -> SyntaxElement {
+        match self {
+            FileReferenceNode::Name(it) => it.syntax().clone().into(),
+            FileReferenceNode::NameRef(it) => it.syntax().clone().into(),
+            FileReferenceNode::Lifetime(it) => it.syntax().clone().into(),
+            FileReferenceNode::FormatStringEntry(it, _) => it.syntax().clone().into(),
+        }
+    }
+    pub fn into_name_like(self) -> Option<ast::NameLike> {
+        match self {
+            FileReferenceNode::Name(it) => Some(ast::NameLike::Name(it)),
+            FileReferenceNode::NameRef(it) => Some(ast::NameLike::NameRef(it)),
+            FileReferenceNode::Lifetime(it) => Some(ast::NameLike::Lifetime(it)),
+            FileReferenceNode::FormatStringEntry(_, _) => None,
+        }
+    }
+    pub fn as_name_ref(&self) -> Option<&ast::NameRef> {
+        match self {
+            FileReferenceNode::NameRef(name_ref) => Some(name_ref),
+            _ => None,
+        }
+    }
+    pub fn as_lifetime(&self) -> Option<&ast::Lifetime> {
+        match self {
+            FileReferenceNode::Lifetime(lifetime) => Some(lifetime),
+            _ => None,
+        }
+    }
+    pub fn text(&self) -> syntax::TokenText<'_> {
+        match self {
+            FileReferenceNode::NameRef(name_ref) => name_ref.text(),
+            FileReferenceNode::Name(name) => name.text(),
+            FileReferenceNode::Lifetime(lifetime) => lifetime.text(),
+            FileReferenceNode::FormatStringEntry(it, range) => {
+                syntax::TokenText::borrowed(&it.text()[*range - it.syntax().text_range().start()])
+            }
+        }
+    }
+}
+
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
 pub enum ReferenceCategory {
     // FIXME: Add this variant and delete the `retain_adt_literal_usages` function.
@@ -467,7 +524,9 @@ 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(token, offset).into_iter().filter_map(|it| it.parent())
+                    sema.descend_into_macros(DescendPreference::None, token)
+                        .into_iter()
+                        .filter_map(|it| it.parent())
                 })
         };
 
@@ -477,6 +536,17 @@ impl<'a> FindUsages<'a> {
 
             // Search for occurrences of the items name
             for offset in match_indices(&text, finder, search_range) {
+                tree.token_at_offset(offset).into_iter().for_each(|token| {
+                    let Some(str_token) = ast::String::cast(token.clone()) else { return };
+                    if let Some((range, nameres)) =
+                        sema.check_for_format_args_template(token.clone(), offset)
+                    {
+                        if self.found_format_args_ref(file_id, range, str_token, nameres, sink) {
+                            return;
+                        }
+                    }
+                });
+
                 for name in find_nodes(name, &tree, offset).filter_map(ast::NameLike::cast) {
                     if match name {
                         ast::NameLike::NameRef(name_ref) => self.found_name_ref(&name_ref, sink),
@@ -591,7 +661,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
+                    name: FileReferenceNode::NameRef(name_ref.clone()),
                     category: None,
                 };
                 sink(file_id, reference)
@@ -610,7 +680,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
+                    name: FileReferenceNode::NameRef(name_ref.clone()),
                     category: is_name_ref_in_import(name_ref).then_some(ReferenceCategory::Import),
                 };
                 sink(file_id, reference)
@@ -619,6 +689,27 @@ impl<'a> FindUsages<'a> {
         }
     }
 
+    fn found_format_args_ref(
+        &self,
+        file_id: FileId,
+        range: TextRange,
+        token: ast::String,
+        res: Option<PathResolution>,
+        sink: &mut dyn FnMut(FileId, FileReference) -> bool,
+    ) -> bool {
+        match res.map(Definition::from) {
+            Some(def) if def == self.def => {
+                let reference = FileReference {
+                    range,
+                    name: FileReferenceNode::FormatStringEntry(token, range),
+                    category: Some(ReferenceCategory::Read),
+                };
+                sink(file_id, reference)
+            }
+            _ => false,
+        }
+    }
+
     fn found_lifetime(
         &self,
         lifetime: &ast::Lifetime,
@@ -629,7 +720,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(lifetime.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::Lifetime(lifetime.clone()),
+                    name: FileReferenceNode::Lifetime(lifetime.clone()),
                     category: None,
                 };
                 sink(file_id, reference)
@@ -653,7 +744,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
+                    name: FileReferenceNode::NameRef(name_ref.clone()),
                     category: ReferenceCategory::new(&def, name_ref),
                 };
                 sink(file_id, reference)
@@ -669,7 +760,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
+                    name: FileReferenceNode::NameRef(name_ref.clone()),
                     category: ReferenceCategory::new(&def, name_ref),
                 };
                 sink(file_id, reference)
@@ -679,7 +770,7 @@ impl<'a> FindUsages<'a> {
                     let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
                     let reference = FileReference {
                         range,
-                        name: ast::NameLike::NameRef(name_ref.clone()),
+                        name: FileReferenceNode::NameRef(name_ref.clone()),
                         category: ReferenceCategory::new(&def, name_ref),
                     };
                     sink(file_id, reference)
@@ -703,7 +794,7 @@ impl<'a> FindUsages<'a> {
                 };
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::NameRef(name_ref.clone()),
+                    name: FileReferenceNode::NameRef(name_ref.clone()),
                     category: access,
                 };
                 sink(file_id, reference)
@@ -726,7 +817,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::Name(name.clone()),
+                    name: FileReferenceNode::Name(name.clone()),
                     // FIXME: mutable patterns should have `Write` access
                     category: Some(ReferenceCategory::Read),
                 };
@@ -736,7 +827,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::Name(name.clone()),
+                    name: FileReferenceNode::Name(name.clone()),
                     category: None,
                 };
                 sink(file_id, reference)
@@ -761,7 +852,7 @@ impl<'a> FindUsages<'a> {
                 let FileRange { file_id, range } = self.sema.original_range(name.syntax());
                 let reference = FileReference {
                     range,
-                    name: ast::NameLike::Name(name.clone()),
+                    name: FileReferenceNode::Name(name.clone()),
                     category: None,
                 };
                 sink(file_id, reference)
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 6f41f51f80e..5cc64e60ed8 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -1,6 +1,6 @@
 //! Entry point for call-hierarchy
 
-use hir::Semantics;
+use hir::{DescendPreference, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     helpers::pick_best_token,
@@ -87,7 +87,7 @@ pub(crate) fn outgoing_calls(
     })?;
     let mut calls = CallLocations::default();
 
-    sema.descend_into_macros(token, offset)
+    sema.descend_into_macros(DescendPreference::None, 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 ac15b6aba61..9760f9daf0a 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -12,7 +12,9 @@ use pulldown_cmark_to_cmark::{cmark_resume_with_options, Options as CMarkOptions
 use stdx::format_to;
 use url::Url;
 
-use hir::{db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, HasAttrs};
+use hir::{
+    db::HirDatabase, Adt, AsAssocItem, AssocItem, AssocItemContainer, DescendPreference, HasAttrs,
+};
 use ide_db::{
     base_db::{CrateOrigin, LangCrateOrigin, ReleaseChannel, SourceDatabase},
     defs::{Definition, NameClass, NameRefClass},
@@ -144,7 +146,7 @@ pub(crate) fn external_docs(
         kind if kind.is_trivia() => 0,
         _ => 1,
     })?;
-    let token = sema.descend_into_macros_single(token, offset);
+    let token = sema.descend_into_macros_single(DescendPreference::None, token);
 
     let node = token.parent()?;
     let definition = match_ast! {
@@ -286,7 +288,7 @@ impl DocCommentToken {
         let original_start = doc_token.text_range().start();
         let relative_comment_offset = offset - original_start - prefix_len;
 
-        sema.descend_into_macros(doc_token, offset).into_iter().find_map(|t| {
+        sema.descend_into_macros(DescendPreference::None, 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 a70f335ada3..653d9f08838 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -1,4 +1,4 @@
-use hir::{HirFileIdExt, InFile, Semantics};
+use hir::{DescendPreference, HirFileIdExt, InFile, Semantics};
 use ide_db::{
     base_db::FileId, helpers::pick_best_token,
     syntax_helpers::insert_whitespace_into_node::insert_ws_into, RootDatabase,
@@ -40,8 +40,10 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<
     // struct Bar;
     // ```
 
-    let derive =
-        sema.descend_into_macros(tok.clone(), 0.into()).into_iter().find_map(|descended| {
+    let derive = sema
+        .descend_into_macros(DescendPreference::None, tok.clone())
+        .into_iter()
+        .find_map(|descended| {
             let hir_file = sema.hir_file_for(&descended.parent()?);
             if !hir_file.is_derive_attr_pseudo_expansion(db) {
                 return None;
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 9b2ff070c74..b706e959d34 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::Semantics;
+use hir::{DescendPreference, Semantics};
 use ide_db::RootDatabase;
 use syntax::{
     algo::{self, skip_trivia_token},
@@ -141,9 +141,9 @@ fn extend_tokens_from_range(
     // compute original mapped token range
     let extended = {
         let fst_expanded =
-            sema.descend_into_macros_single(first_token.clone(), original_range.start());
+            sema.descend_into_macros_single(DescendPreference::None, first_token.clone());
         let lst_expanded =
-            sema.descend_into_macros_single(last_token.clone(), original_range.end());
+            sema.descend_into_macros_single(DescendPreference::None, last_token.clone());
         let mut lca =
             algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?;
         lca = shallowest_node(&lca);
@@ -154,10 +154,10 @@ fn extend_tokens_from_range(
     };
 
     // Compute parent node range
-    let validate = |offset: TextSize| {
+    let validate = || {
         let extended = &extended;
         move |token: &SyntaxToken| -> bool {
-            let expanded = sema.descend_into_macros_single(token.clone(), offset);
+            let expanded = sema.descend_into_macros_single(DescendPreference::None, token.clone());
             let parent = match expanded.parent() {
                 Some(it) => it,
                 None => return false,
@@ -171,14 +171,14 @@ fn extend_tokens_from_range(
         let token = token.prev_token()?;
         skip_trivia_token(token, Direction::Prev)
     })
-    .take_while(validate(original_range.start()))
+    .take_while(validate())
     .last()?;
 
     let last = successors(Some(last_token), |token| {
         let token = token.next_token()?;
         skip_trivia_token(token, Direction::Next)
     })
-    .take_while(validate(original_range.end()))
+    .take_while(validate())
     .last()?;
 
     let range = first.text_range().cover(last.text_range());
diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs
index 7e0fab42608..ad7ec196456 100644
--- a/crates/ide/src/goto_declaration.rs
+++ b/crates/ide/src/goto_declaration.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, Semantics};
+use hir::{AsAssocItem, DescendPreference, 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(original_token, offset)
+        .descend_into_macros(DescendPreference::None, 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 816f1bebee4..5ca82a362f5 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -4,7 +4,7 @@ use crate::{
     doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
     RangeInfo, TryToNav,
 };
-use hir::{AsAssocItem, AssocItem, Semantics};
+use hir::{AsAssocItem, AssocItem, DescendPreference, Semantics};
 use ide_db::{
     base_db::{AnchoredPath, FileId, FileLoader},
     defs::{Definition, IdentClass},
@@ -55,8 +55,21 @@ pub(crate) fn goto_definition(
             Some(RangeInfo::new(link_range, vec![nav]))
         });
     }
+
+    if let Some((range, resolution)) =
+        sema.check_for_format_args_template(original_token.clone(), offset)
+    {
+        return Some(RangeInfo::new(
+            range,
+            match resolution {
+                Some(res) => def_to_nav(db, Definition::from(res)),
+                None => vec![],
+            },
+        ));
+    }
+
     let navs = sema
-        .descend_into_macros(original_token.clone(), offset)
+        .descend_into_macros(DescendPreference::None, original_token.clone())
         .into_iter()
         .filter_map(|token| {
             let parent = token.parent()?;
@@ -809,18 +822,13 @@ mod confuse_index { fn foo(); }
     fn goto_through_format() {
         check(
             r#"
+//- minicore: fmt
 #[macro_export]
 macro_rules! format {
     ($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
 }
-#[rustc_builtin_macro]
-#[macro_export]
-macro_rules! format_args {
-    ($fmt:expr) => ({ /* compiler built-in */ });
-    ($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
-}
 pub mod __export {
-    pub use crate::format_args;
+    pub use core::format_args;
     fn foo() {} // for index confusion
 }
 fn foo() -> i8 {}
@@ -2059,4 +2067,18 @@ fn f2() {
 "#,
         );
     }
+
+    #[test]
+    fn implicit_format_args() {
+        check(
+            r#"
+//- minicore: fmt
+fn test() {
+    let a = "world";
+     // ^
+    format_args!("hello {a$0}");
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 3593c5c7dd9..bb474282dd7 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, Impl, Semantics};
+use hir::{AsAssocItem, DescendPreference, Impl, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     helpers::pick_best_token,
@@ -34,7 +34,7 @@ pub(crate) fn goto_implementation(
     })?;
     let range = original_token.text_range();
     let navs =
-        sema.descend_into_macros(original_token, offset)
+        sema.descend_into_macros(DescendPreference::None, original_token)
             .into_iter()
             .filter_map(|token| token.parent().and_then(ast::NameLike::cast))
             .filter_map(|node| match &node {
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 955923d7691..83f134aaaf5 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -1,3 +1,4 @@
+use hir::{DescendPreference, GenericParam};
 use ide_db::{base_db::Upcast, defs::Definition, helpers::pick_best_token, RootDatabase};
 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, T};
 
@@ -36,8 +37,37 @@ pub(crate) fn goto_type_definition(
             }
         }
     };
+    let mut process_ty = |ty: hir::Type| {
+        // collect from each `ty` into the `res` result vec
+        let ty = ty.strip_references();
+        ty.walk(db, |t| {
+            if let Some(adt) = t.as_adt() {
+                push(adt.into());
+            } else if let Some(trait_) = t.as_dyn_trait() {
+                push(trait_.into());
+            } else if let Some(traits) = t.as_impl_traits(db) {
+                traits.for_each(|it| push(it.into()));
+            } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
+                push(trait_.into());
+            }
+        });
+    };
+    if let Some((range, resolution)) = sema.check_for_format_args_template(token.clone(), offset) {
+        if let Some(ty) = resolution.and_then(|res| match Definition::from(res) {
+            Definition::Const(it) => Some(it.ty(db)),
+            Definition::Static(it) => Some(it.ty(db)),
+            Definition::GenericParam(GenericParam::ConstParam(it)) => Some(it.ty(db)),
+            Definition::Local(it) => Some(it.ty(db)),
+            Definition::Adt(hir::Adt::Struct(it)) => Some(it.ty(db)),
+            _ => None,
+        }) {
+            process_ty(ty);
+        }
+        return Some(RangeInfo::new(range, res));
+    }
+
     let range = token.text_range();
-    sema.descend_into_macros(token, offset)
+    sema.descend_into_macros(DescendPreference::None, token)
         .into_iter()
         .filter_map(|token| {
             let ty = sema
@@ -75,21 +105,7 @@ pub(crate) fn goto_type_definition(
                 });
             ty
         })
-        .for_each(|ty| {
-            // collect from each `ty` into the `res` result vec
-            let ty = ty.strip_references();
-            ty.walk(db, |t| {
-                if let Some(adt) = t.as_adt() {
-                    push(adt.into());
-                } else if let Some(trait_) = t.as_dyn_trait() {
-                    push(trait_.into());
-                } else if let Some(traits) = t.as_impl_traits(db) {
-                    traits.for_each(|it| push(it.into()));
-                } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
-                    push(trait_.into());
-                }
-            });
-        });
+        .for_each(process_ty);
     Some(RangeInfo::new(range, res))
 }
 
@@ -328,4 +344,40 @@ fn foo(x$0: Bar<Baz<Foo>, Baz<usize>) {}
 "#,
         );
     }
+
+    #[test]
+    fn implicit_format_args() {
+        check(
+            r#"
+//- minicore: fmt
+struct Bar;
+    // ^^^
+    fn test() {
+    let a = Bar;
+    format_args!("hello {a$0}");
+}
+"#,
+        );
+        check(
+            r#"
+//- minicore: fmt
+struct Bar;
+    // ^^^
+    fn test() {
+    format_args!("hello {Bar$0}");
+}
+"#,
+        );
+        check(
+            r#"
+//- minicore: fmt
+struct Bar;
+    // ^^^
+const BAR: Bar = Bar;
+fn test() {
+    format_args!("hello {BAR$0}");
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index a7f5ae92a4c..8daff8c2ebe 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -1,4 +1,6 @@
-use hir::Semantics;
+use std::iter;
+
+use hir::{DescendPreference, Semantics};
 use ide_db::{
     base_db::{FileId, FilePosition, FileRange},
     defs::{Definition, IdentClass},
@@ -15,7 +17,6 @@ use syntax::{
     SyntaxKind::{self, IDENT, INT_NUMBER},
     SyntaxNode, SyntaxToken, TextRange, T,
 };
-use text_edit::TextSize;
 
 use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav};
 
@@ -132,7 +133,16 @@ fn highlight_references(
     token: SyntaxToken,
     FilePosition { file_id, offset }: FilePosition,
 ) -> Option<Vec<HighlightedRange>> {
-    let defs = find_defs(sema, token.clone(), offset);
+    let defs = if let Some((range, resolution)) =
+        sema.check_for_format_args_template(token.clone(), offset)
+    {
+        match resolution.map(Definition::from) {
+            Some(def) => iter::once(def).collect(),
+            None => return Some(vec![HighlightedRange { range, category: None }]),
+        }
+    } else {
+        find_defs(sema, token.clone())
+    };
     let usages = defs
         .iter()
         .filter_map(|&d| {
@@ -456,12 +466,8 @@ fn cover_range(r0: Option<TextRange>, r1: Option<TextRange>) -> Option<TextRange
     }
 }
 
-fn find_defs(
-    sema: &Semantics<'_, RootDatabase>,
-    token: SyntaxToken,
-    offset: TextSize,
-) -> FxHashSet<Definition> {
-    sema.descend_into_macros(token, offset)
+fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSet<Definition> {
+    sema.descend_into_macros(DescendPreference::None, token)
         .into_iter()
         .filter_map(|token| IdentClass::classify_token(sema, &token))
         .map(IdentClass::definitions_no_ops)
@@ -1623,4 +1629,21 @@ fn f2<T: Foo>(t: T) {
 "#,
         );
     }
+
+    #[test]
+    fn implicit_format_args() {
+        check(
+            r#"
+//- minicore: fmt
+fn test() {
+    let a = "foo";
+     // ^
+    format_args!("hello {a} {a$0} {}", a);
+                      // ^read
+                          // ^read
+                                  // ^read
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index e0b64fe7988..88a5b623425 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -6,7 +6,7 @@ mod tests;
 use std::iter;
 
 use either::Either;
-use hir::{db::DefDatabase, HasSource, LangItem, Semantics};
+use hir::{db::DefDatabase, DescendPreference, HasSource, LangItem, Semantics};
 use ide_db::{
     base_db::FileRange,
     defs::{Definition, IdentClass, NameRefClass, OperatorClass},
@@ -150,6 +150,19 @@ fn hover_simple(
         });
     }
 
+    if let Some((range, resolution)) =
+        sema.check_for_format_args_template(original_token.clone(), offset)
+    {
+        let res = hover_for_definition(
+            sema,
+            file_id,
+            Definition::from(resolution?),
+            &original_token.parent()?,
+            config,
+        )?;
+        return Some(RangeInfo::new(range, res));
+    }
+
     let in_attr = original_token
         .parent_ancestors()
         .filter_map(ast::Item::cast)
@@ -161,11 +174,10 @@ fn hover_simple(
 
     // prefer descending the same token kind in attribute expansions, in normal macros text
     // equivalency is more important
-    let descended = if in_attr {
-        [sema.descend_into_macros_with_kind_preference(original_token.clone(), offset)].into()
-    } else {
-        sema.descend_into_macros_with_same_text(original_token.clone(), offset)
-    };
+    let descended = sema.descend_into_macros(
+        if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText },
+        original_token.clone(),
+    );
     let descended = || descended.iter();
 
     let result = descended()
@@ -298,11 +310,11 @@ pub(crate) fn hover_for_definition(
     sema: &Semantics<'_, RootDatabase>,
     file_id: FileId,
     definition: Definition,
-    node: &SyntaxNode,
+    scope_node: &SyntaxNode,
     config: &HoverConfig,
 ) -> Option<HoverResult> {
     let famous_defs = match &definition {
-        Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
+        Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(scope_node)?.krate())),
         _ => None,
     };
     render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index d3d492f3fde..8c9d58671e6 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -6613,3 +6613,88 @@ fn test() {
         "#]],
     );
 }
+
+#[test]
+fn format_args_implicit() {
+    check(
+        r#"
+//- minicore: fmt
+fn test() {
+let aaaaa = "foo";
+format_args!("{aaaaa$0}");
+}
+"#,
+        expect![[r#"
+            *aaaaa*
+
+            ```rust
+            let aaaaa: &str // size = 16 (0x10), align = 8, niches = 1
+            ```
+        "#]],
+    );
+}
+
+#[test]
+fn format_args_implicit2() {
+    check(
+        r#"
+//- minicore: fmt
+fn test() {
+let aaaaa = "foo";
+format_args!("{$0aaaaa}");
+}
+"#,
+        expect![[r#"
+            *aaaaa*
+
+            ```rust
+            let aaaaa: &str // size = 16 (0x10), align = 8, niches = 1
+            ```
+        "#]],
+    );
+}
+
+#[test]
+fn format_args_implicit_raw() {
+    check(
+        r#"
+//- minicore: fmt
+fn test() {
+let aaaaa = "foo";
+format_args!(r"{$0aaaaa}");
+}
+"#,
+        expect![[r#"
+            *aaaaa*
+
+            ```rust
+            let aaaaa: &str // size = 16 (0x10), align = 8, niches = 1
+            ```
+        "#]],
+    );
+}
+
+#[test]
+fn format_args_implicit_nested() {
+    check(
+        r#"
+//- minicore: fmt
+macro_rules! foo {
+    ($($tt:tt)*) => {
+        format_args!($($tt)*)
+    }
+}
+fn test() {
+let aaaaa = "foo";
+foo!(r"{$0aaaaa}");
+}
+"#,
+        expect![[r#"
+            *aaaaa*
+
+            ```rust
+            let aaaaa: &str // size = 16 (0x10), align = 8, niches = 1
+            ```
+        "#]],
+    );
+}
diff --git a/crates/ide/src/moniker.rs b/crates/ide/src/moniker.rs
index 2ca2b5b1d5f..8e8bb5e0139 100644
--- a/crates/ide/src/moniker.rs
+++ b/crates/ide/src/moniker.rs
@@ -1,7 +1,7 @@
 //! This module generates [moniker](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/#exportsImports)
 //! for LSIF and LSP.
 
-use hir::{AsAssocItem, AssocItemContainer, Crate, Semantics};
+use hir::{AsAssocItem, AssocItemContainer, Crate, DescendPreference, Semantics};
 use ide_db::{
     base_db::{CrateOrigin, FilePosition, LangCrateOrigin},
     defs::{Definition, IdentClass},
@@ -99,7 +99,7 @@ pub(crate) fn moniker(
         });
     }
     let navs = sema
-        .descend_into_macros(original_token.clone(), offset)
+        .descend_into_macros(DescendPreference::None, 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 f387bbf6b01..b805ddfa267 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::{PathResolution, Semantics};
+use hir::{DescendPreference, PathResolution, Semantics};
 use ide_db::{
     base_db::FileId,
     defs::{Definition, NameClass, NameRefClass},
@@ -109,7 +109,7 @@ pub(crate) fn find_all_refs(
         }
         None => {
             let search = make_searcher(false);
-            Some(find_defs(sema, &syntax, position.offset)?.map(search).collect())
+            Some(find_defs(sema, &syntax, position.offset)?.into_iter().map(search).collect())
         }
     }
 }
@@ -118,15 +118,27 @@ pub(crate) fn find_defs<'a>(
     sema: &'a Semantics<'_, RootDatabase>,
     syntax: &SyntaxNode,
     offset: TextSize,
-) -> Option<impl Iterator<Item = Definition> + 'a> {
+) -> Option<impl IntoIterator<Item = Definition> + 'a> {
     let token = syntax.token_at_offset(offset).find(|t| {
         matches!(
             t.kind(),
-            IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self]
+            IDENT
+                | INT_NUMBER
+                | LIFETIME_IDENT
+                | STRING
+                | T![self]
+                | T![super]
+                | T![crate]
+                | T![Self]
         )
-    });
-    token.map(|token| {
-        sema.descend_into_macros_with_same_text(token, offset)
+    })?;
+
+    if let Some((_, resolution)) = sema.check_for_format_args_template(token.clone(), offset) {
+        return resolution.map(Definition::from).map(|it| vec![it]);
+    }
+
+    Some(
+        sema.descend_into_macros(DescendPreference::SameText, token)
             .into_iter()
             .filter_map(|it| ast::NameLike::cast(it.parent()?))
             .filter_map(move |name_like| {
@@ -162,7 +174,8 @@ pub(crate) fn find_defs<'a>(
                 };
                 Some(def)
             })
-    })
+            .collect(),
+    )
 }
 
 pub(crate) fn decl_mutability(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> bool {
@@ -2092,4 +2105,27 @@ fn main() { r#fn(); }
             "#]],
         );
     }
+
+    #[test]
+    fn implicit_format_args() {
+        check(
+            r#"
+//- minicore: fmt
+fn test() {
+    let a = "foo";
+    format_args!("hello {a} {a$0} {}", a);
+                      // ^
+                          // ^
+                                   // ^
+}
+"#,
+            expect![[r#"
+                a Local FileId(0) 20..21 20..21
+
+                FileId(0) 56..57 Read
+                FileId(0) 60..61 Read
+                FileId(0) 68..69 Read
+            "#]],
+        );
+    }
 }
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index ad1eb249971..1febfabfcb7 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -6,14 +6,16 @@
 
 use hir::{AsAssocItem, HirFileIdExt, InFile, Semantics};
 use ide_db::{
-    base_db::FileId,
+    base_db::{FileId, FileRange},
     defs::{Definition, NameClass, NameRefClass},
     rename::{bail, format_err, source_edit_from_references, IdentifierKind},
     RootDatabase,
 };
 use itertools::Itertools;
 use stdx::{always, never};
-use syntax::{ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxNode, TextRange, TextSize};
+use syntax::{
+    ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxKind, SyntaxNode, TextRange, TextSize,
+};
 
 use text_edit::TextEdit;
 
@@ -34,23 +36,20 @@ pub(crate) fn prepare_rename(
     let syntax = source_file.syntax();
 
     let res = find_definitions(&sema, syntax, position)?
-        .map(|(name_like, def)| {
+        .map(|(frange, kind, def)| {
             // ensure all ranges are valid
 
             if def.range_for_rename(&sema).is_none() {
                 bail!("No references found at position")
             }
-            let Some(frange) = sema.original_range_opt(name_like.syntax()) else {
-                bail!("No references found at position");
-            };
 
             always!(
                 frange.range.contains_inclusive(position.offset)
                     && frange.file_id == position.file_id
             );
 
-            Ok(match name_like {
-                ast::NameLike::Lifetime(_) => {
+            Ok(match kind {
+                SyntaxKind::LIFETIME => {
                     TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())
                 }
                 _ => frange.range,
@@ -93,7 +92,7 @@ pub(crate) fn rename(
     let defs = find_definitions(&sema, syntax, position)?;
 
     let ops: RenameResult<Vec<SourceChange>> = defs
-        .map(|(_namelike, def)| {
+        .map(|(.., def)| {
             if let Definition::Local(local) = def {
                 if let Some(self_param) = local.as_self_param(sema.db) {
                     cov_mark::hit!(rename_self_to_param);
@@ -134,11 +133,27 @@ pub(crate) fn will_rename_file(
 fn find_definitions(
     sema: &Semantics<'_, RootDatabase>,
     syntax: &SyntaxNode,
-    position: FilePosition,
-) -> RenameResult<impl Iterator<Item = (ast::NameLike, Definition)>> {
-    let symbols = sema
-        .find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
-        .map(|name_like| {
+    FilePosition { file_id, offset }: FilePosition,
+) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition)>> {
+    let token = syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));
+
+    if let Some((range, Some(resolution))) =
+        token.and_then(|token| sema.check_for_format_args_template(token, offset))
+    {
+        return Ok(vec![(
+            FileRange { file_id, range },
+            SyntaxKind::STRING,
+            Definition::from(resolution),
+        )]
+        .into_iter());
+    }
+
+    let symbols =
+        sema.find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, offset).map(|name_like| {
+            let kind = name_like.syntax().kind();
+            let range = sema
+                .original_range_opt(name_like.syntax())
+                .ok_or_else(|| format_err!("No references found at position"))?;
             let res = match &name_like {
                 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
                 ast::NameLike::Name(name)
@@ -163,7 +178,6 @@ fn find_definitions(
                             Definition::Local(local_def)
                         }
                     })
-                    .map(|def| (name_like.clone(), def))
                     .ok_or_else(|| format_err!("No references found at position")),
                 ast::NameLike::NameRef(name_ref) => {
                     NameRefClass::classify(sema, name_ref)
@@ -187,7 +201,7 @@ fn find_definitions(
                             {
                                 Err(format_err!("Renaming aliases is currently unsupported"))
                             } else {
-                                Ok((name_like.clone(), def))
+                                Ok(def)
                             }
                         })
                 }
@@ -203,11 +217,10 @@ fn find_definitions(
                                 _ => None,
                             })
                         })
-                        .map(|def| (name_like, def))
                         .ok_or_else(|| format_err!("No references found at position"))
                 }
             };
-            res
+            res.map(|def| (range, kind, def))
         });
 
     let res: RenameResult<Vec<_>> = symbols.collect();
@@ -218,7 +231,7 @@ fn find_definitions(
                 Err(format_err!("No references found at position"))
             } else {
                 // remove duplicates, comparing `Definition`s
-                Ok(v.into_iter().unique_by(|t| t.1))
+                Ok(v.into_iter().unique_by(|&(.., def)| def).collect::<Vec<_>>().into_iter())
             }
         }
         Err(e) => Err(e),
@@ -2663,4 +2676,44 @@ struct A;
             "error: Cannot rename a non-local definition.",
         )
     }
+
+    #[test]
+    fn implicit_format_args() {
+        check(
+            "fbar",
+            r#"
+//- minicore: fmt
+fn test() {
+    let foo = "foo";
+    format_args!("hello {foo} {foo$0} {}", foo);
+}
+"#,
+            r#"
+fn test() {
+    let fbar = "foo";
+    format_args!("hello {fbar} {fbar} {}", fbar);
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn implicit_format_args2() {
+        check(
+            "fo",
+            r#"
+//- minicore: fmt
+fn test() {
+    let foo = "foo";
+    format_args!("hello {foo} {foo$0} {}", foo);
+}
+"#,
+            r#"
+fn test() {
+    let fo = "foo";
+    format_args!("hello {fo} {fo} {}", fo);
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 954a364c787..d487a538bb5 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -9,7 +9,7 @@ use ide_db::{
     defs::Definition,
     documentation::docs_from_attrs,
     helpers::visit_file_defs,
-    search::SearchScope,
+    search::{FileReferenceNode, SearchScope},
     FxHashMap, FxHashSet, RootDatabase, SymbolKind,
 };
 use itertools::Itertools;
@@ -240,7 +240,7 @@ fn find_related_tests(
             .flatten();
         for ref_ in defs {
             let name_ref = match ref_.name {
-                ast::NameLike::NameRef(name_ref) => name_ref,
+                FileReferenceNode::NameRef(name_ref) => name_ref,
                 _ => continue,
             };
             if let Some(fn_def) =
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs
index e020b52e171..990376a4965 100644
--- a/crates/ide/src/signature_help.rs
+++ b/crates/ide/src/signature_help.rs
@@ -4,7 +4,10 @@
 use std::collections::BTreeSet;
 
 use either::Either;
-use hir::{AssocItem, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait};
+use hir::{
+    AssocItem, DescendPreference, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics,
+    Trait,
+};
 use ide_db::{
     active_parameter::{callable_for_node, generic_def_for_node},
     base_db::FilePosition,
@@ -79,7 +82,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(token, offset);
+    let token = sema.descend_into_macros_single(DescendPreference::None, token);
 
     for node in token.parent_ancestors() {
         match_ast! {
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index dd72484b380..366a3c969f9 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -13,7 +13,7 @@ mod html;
 #[cfg(test)]
 mod tests;
 
-use hir::{Name, Semantics};
+use hir::{DescendPreference, Name, Semantics};
 use ide_db::{FxHashMap, RootDatabase, SymbolKind};
 use syntax::{
     ast::{self, IsString},
@@ -393,13 +393,24 @@ fn traverse(
             // Attempt to descend tokens into macro-calls.
             let res = match element {
                 NodeOrToken::Token(token) if token.kind() != COMMENT => {
-                    let token = match attr_or_derive_item {
-                        Some(AttrOrDerive::Attr(_)) => {
-                            sema.descend_into_macros_with_kind_preference(token, 0.into())
-                        }
-                        Some(AttrOrDerive::Derive(_)) | None => {
-                            sema.descend_into_macros_single(token, 0.into())
-                        }
+                    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(
+                            match attr_or_derive_item {
+                                Some(AttrOrDerive::Attr(_)) => DescendPreference::SameKind,
+                                Some(AttrOrDerive::Derive(_)) | None => DescendPreference::None,
+                            },
+                            token,
+                        )
                     };
                     match token.parent().and_then(ast::NameLike::cast) {
                         // Remap the token into the wrapping single token nodes
@@ -441,7 +452,7 @@ fn traverse(
                     {
                         continue;
                     }
-                    highlight_format_string(hl, &string, &expanded_string, range);
+                    highlight_format_string(hl, sema, krate, &string, &expanded_string, range);
 
                     if !string.is_raw() {
                         highlight_escape_string(hl, &string, range.start());
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs
index 2ef1315945a..518e7145479 100644
--- a/crates/ide/src/syntax_highlighting/format.rs
+++ b/crates/ide/src/syntax_highlighting/format.rs
@@ -1,14 +1,20 @@
 //! Syntax highlighting for format macro strings.
 use ide_db::{
+    defs::Definition,
     syntax_helpers::format_string::{is_format_string, lex_format_specifiers, FormatSpecifier},
     SymbolKind,
 };
 use syntax::{ast, TextRange};
 
-use crate::{syntax_highlighting::highlights::Highlights, HlRange, HlTag};
+use crate::{
+    syntax_highlighting::{highlight::highlight_def, highlights::Highlights},
+    HlRange, HlTag,
+};
 
 pub(super) fn highlight_format_string(
     stack: &mut Highlights,
+    sema: &hir::Semantics<'_, ide_db::RootDatabase>,
+    krate: hir::Crate,
     string: &ast::String,
     expanded_string: &ast::String,
     range: TextRange,
@@ -27,6 +33,18 @@ pub(super) fn highlight_format_string(
             });
         }
     });
+
+    if let Some(parts) = sema.as_format_args_parts(string) {
+        parts.into_iter().for_each(|(range, res)| {
+            if let Some(res) = res {
+                stack.add(HlRange {
+                    range,
+                    highlight: highlight_def(sema, krate, Definition::from(res)),
+                    binding_hash: None,
+                })
+            }
+        })
+    }
 }
 
 fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HlTag> {
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 1bffab29cf9..d510c11c3d5 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -348,7 +348,7 @@ fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
     hash((name, shadow_count))
 }
 
-fn highlight_def(
+pub(super) fn highlight_def(
     sema: &Semantics<'_, RootDatabase>,
     krate: hir::Crate,
     def: Definition,
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 06b66b302ae..b8d38a60fc0 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_macros.html
@@ -90,17 +90,11 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="brace">}</span>
 <span class="brace">}</span>
 
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">concat</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">include</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args</span> <span class="brace">{</span><span class="brace">}</span>
 
-<span class="macro">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation 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="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation 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">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
-    <span class="macro">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
     <span class="macro">dont_color_me_braces</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
     <span class="macro">noop</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro macro">noop</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index aa79cd9b829..75cb6223e0e 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -48,47 +48,38 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
         <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>io<span class="colon">:</span><span class="colon">:</span>_print<span class="parenthesis">(</span>format_args_nl<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">*</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="brace">}</span><span class="parenthesis">)</span>
 <span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">format_args_nl</span> <span class="brace">{</span><span class="brace">}</span>
 
 <span class="keyword">mod</span> <span class="module declaration">panic</span> <span class="brace">{</span>
     <span class="keyword">pub</span> <span class="keyword">macro</span> <span class="macro declaration">panic_2015</span> <span class="brace">{</span>
         <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
-            <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="string_literal">"explicit panic"</span><span class="parenthesis">)</span>
+            panic<span class="parenthesis">(</span><span class="string_literal">"explicit panic"</span><span class="parenthesis">)</span>
         <span class="parenthesis">)</span><span class="comma">,</span>
         <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>literal <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
-            <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+            panic<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
         <span class="parenthesis">)</span><span class="comma">,</span>
         <span class="comment">// Use `panic_str` instead of `panic_display::&lt;&str&gt;` for non_fmt_panic lint.</span>
         <span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
-            <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_str<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
+            panic_str<span class="parenthesis">(</span><span class="punctuation">$</span>msg<span class="parenthesis">)</span>
         <span class="parenthesis">)</span><span class="comma">,</span>
         <span class="comment">// Special-case the single-argument case for const_panic.</span>
         <span class="parenthesis">(</span><span class="string_literal">"{}"</span><span class="comma">,</span> <span class="punctuation">$</span>arg<span class="colon">:</span>expr <span class="punctuation">$</span><span class="parenthesis">(</span><span class="comma">,</span><span class="parenthesis">)</span><span class="punctuation">?</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
-            <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_display<span class="parenthesis">(</span><span class="punctuation">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span>
+            panic_display<span class="parenthesis">(</span><span class="punctuation">&</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span>
         <span class="parenthesis">)</span><span class="comma">,</span>
         <span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="colon">:</span>expr<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span>
-            <span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panicking<span class="colon">:</span><span class="colon">:</span>panic_fmt<span class="parenthesis">(</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
+            panic_fmt<span class="parenthesis">(</span>const_format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>fmt<span class="comma">,</span> <span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span>
         <span class="parenthesis">)</span><span class="comma">,</span>
     <span class="brace">}</span>
 <span class="brace">}</span>
 
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="parenthesis attribute">(</span><span class="none attribute">std_panic</span><span class="parenthesis attribute">)</span><span class="attribute_bracket attribute">]</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">macro_export</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">panic</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">assert</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">asm</span> <span class="brace">{</span><span class="brace">}</span>
-<span class="attribute_bracket attribute">#</span><span class="attribute_bracket attribute">[</span><span class="builtin_attr attribute library">rustc_builtin_macro</span><span class="attribute_bracket attribute">]</span>
-<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">concat</span> <span class="brace">{</span><span class="brace">}</span>
-
 <span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">toho</span> <span class="brace">{</span>
     <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented"</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="colon">:</span>tt<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="parenthesis">(</span><span class="punctuation">$</span>crate<span class="colon">:</span><span class="colon">:</span>panic<span class="punctuation">!</span><span class="parenthesis">(</span><span class="string_literal">"not yet implemented: {}"</span><span class="comma">,</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span><span class="parenthesis">(</span><span class="punctuation">$</span>arg<span class="parenthesis">)</span><span class="punctuation">+</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 <span class="brace">}</span>
 
+<span class="keyword">macro_rules</span><span class="macro_bang">!</span> <span class="macro declaration">reuse_twice</span> <span class="brace">{</span>
+    <span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="colon">:</span>literal<span class="parenthesis">)</span> <span class="operator">=</span><span class="angle">&gt;</span> <span class="brace">{</span><span class="brace">{</span>stringify<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</span><span class="semicolon">;</span> format_args<span class="punctuation">!</span><span class="parenthesis">(</span><span class="punctuation">$</span>literal<span class="parenthesis">)</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="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="char_literal">'</span><span class="escape_sequence">\n</span><span class="char_literal">'</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="char_literal">'</span><span class="escape_sequence">\t</span><span class="char_literal">'</span><span class="semicolon">;</span>
@@ -165,20 +156,23 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable declaration macro">ничоси</span> <span class="operator macro">=</span> <span class="numeric_literal macro">92</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
 
     <span class="macro">println</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">:</span><span class="variable">x</span><span class="format_specifier">?</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> "</span><span class="comma macro">,</span> <span class="unresolved_reference macro">thingy</span><span class="comma macro">,</span> <span class="unresolved_reference macro">n2</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"more {}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"{} asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">panic</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"more </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">assert</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="bool_literal macro">true</span><span class="comma macro">,</span> <span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> asdasd"</span><span class="comma macro">,</span> <span class="numeric_literal macro">1</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
     <span class="macro">toho</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}fmt"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">i</span><span class="colon">:</span> <span class="builtin_type">u64</span> <span class="operator">=</span> <span class="numeric_literal">3</span><span class="semicolon">;</span>
     <span class="keyword">let</span> <span class="variable declaration">o</span><span class="colon">:</span> <span class="builtin_type">u64</span><span class="semicolon">;</span>
-    <span class="macro unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span>
-        <span class="string_literal macro">"mov {0}, {1}"</span><span class="comma macro">,</span>
-        <span class="string_literal macro">"add {0}, 5"</span><span class="comma macro">,</span>
+    <span class="macro default_library library unsafe">asm</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span>
+        <span class="string_literal macro">"mov </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">, </span><span class="format_specifier">{</span><span class="numeric_literal">1</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span>
+        <span class="string_literal macro">"add </span><span class="format_specifier">{</span><span class="numeric_literal">0</span><span class="format_specifier">}</span><span class="string_literal macro">, 5"</span><span class="comma macro">,</span>
         <span class="none macro">out</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">o</span><span class="comma macro">,</span>
         <span class="keyword control macro">in</span><span class="parenthesis macro">(</span><span class="none macro">reg</span><span class="parenthesis macro">)</span> <span class="none macro">i</span><span class="comma macro">,</span>
     <span class="parenthesis macro">)</span><span class="semicolon">;</span>
 
-    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
-    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="macro default_library library macro">format_args</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="macro macro">toho</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="keyword">const</span> <span class="constant declaration">CONSTANT</span><span class="colon">:</span> <span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="colon">:</span>
+    <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">m</span> <span class="operator">=</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">format_args</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">"{}"</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="string_literal macro">"{}"</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable reference">backslash</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="constant">CONSTANT</span><span class="format_specifier">}</span><span class="string_literal macro"> </span><span class="format_specifier">{</span><span class="variable mutable">m</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="comma macro">,</span> <span class="macro default_library library macro">format_args</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="comma macro">,</span> <span class="numeric_literal macro">0</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="unresolved_reference macro">foo</span><span class="comma macro">,</span> <span class="string_literal macro">"bar"</span><span class="comma macro">,</span> <span class="macro macro">toho</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="parenthesis macro">)</span><span class="comma macro">,</span> <span class="variable macro reference">backslash</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
+    <span class="macro">reuse_twice</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"</span><span class="format_specifier">{</span><span class="variable reference">backslash</span><span class="format_specifier">}</span><span class="string_literal macro">"</span><span class="parenthesis macro">)</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 542d8992531..fcfd3c92571 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -48,6 +48,7 @@ fn macros() {
     check_highlighting(
         r#"
 //- proc_macros: mirror
+//- minicore: fmt, include, concat
 //- /lib.rs crate:lib
 proc_macros::mirror! {
     {
@@ -96,12 +97,6 @@ macro without_args {
     }
 }
 
-#[rustc_builtin_macro]
-macro_rules! concat {}
-#[rustc_builtin_macro]
-macro_rules! include {}
-#[rustc_builtin_macro]
-macro_rules! format_args {}
 
 include!(concat!("foo/", "foo.rs"));
 
@@ -401,53 +396,44 @@ fn test_string_highlighting() {
     // thus, we have to copy the macro definition from `std`
     check_highlighting(
         r#"
-//- minicore: fmt
+//- minicore: fmt, assert, asm, concat, panic
 macro_rules! println {
     ($($arg:tt)*) => ({
         $crate::io::_print(format_args_nl!($($arg)*));
     })
 }
-#[rustc_builtin_macro]
-#[macro_export]
-macro_rules! format_args_nl {}
 
 mod panic {
     pub macro panic_2015 {
         () => (
-            $crate::panicking::panic("explicit panic")
+            panic("explicit panic")
         ),
         ($msg:literal $(,)?) => (
-            $crate::panicking::panic($msg)
+            panic($msg)
         ),
         // Use `panic_str` instead of `panic_display::<&str>` for non_fmt_panic lint.
         ($msg:expr $(,)?) => (
-            $crate::panicking::panic_str($msg)
+            panic_str($msg)
         ),
         // Special-case the single-argument case for const_panic.
         ("{}", $arg:expr $(,)?) => (
-            $crate::panicking::panic_display(&$arg)
+            panic_display(&$arg)
         ),
         ($fmt:expr, $($arg:tt)+) => (
-            $crate::panicking::panic_fmt(const_format_args!($fmt, $($arg)+))
+            panic_fmt(const_format_args!($fmt, $($arg)+))
         ),
     }
 }
 
-#[rustc_builtin_macro(std_panic)]
-#[macro_export]
-macro_rules! panic {}
-#[rustc_builtin_macro]
-macro_rules! assert {}
-#[rustc_builtin_macro]
-macro_rules! asm {}
-#[rustc_builtin_macro]
-macro_rules! concat {}
-
 macro_rules! toho {
     () => ($crate::panic!("not yet implemented"));
     ($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", format_args!($($arg)+)));
 }
 
+macro_rules! reuse_twice {
+    ($literal:literal) => {{stringify!($literal); format_args!($literal)}};
+}
+
 fn main() {
     let a = '\n';
     let a = '\t';
@@ -538,8 +524,11 @@ fn main() {
         in(reg) i,
     );
 
+    const CONSTANT: () = ():
+    let mut m = ();
     format_args!(concat!("{}"), "{}");
-    format_args!("{} {} {} {} {} {}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash);
+    format_args!("{} {} {} {} {} {} {backslash} {CONSTANT} {m}", backslash, format_args!("{}", 0), foo, "bar", toho!(), backslash);
+    reuse_twice!("{backslash}");
 }"#,
         expect_file!["./test_data/highlight_strings.html"],
         false,
diff --git a/crates/mbe/src/token_map.rs b/crates/mbe/src/token_map.rs
index b51d7575a11..28b39b4f1ee 100644
--- a/crates/mbe/src/token_map.rs
+++ b/crates/mbe/src/token_map.rs
@@ -35,6 +35,7 @@ impl<S: Span> SpanMap<S> {
     ///
     /// Note this does a linear search through the entire backing vector.
     pub fn ranges_with_span(&self, span: S) -> impl Iterator<Item = TextRange> + '_ {
+        // FIXME: This should ignore the syntax context!
         self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| {
             if s != span {
                 return None;
diff --git a/crates/syntax/src/ast/token_ext.rs b/crates/syntax/src/ast/token_ext.rs
index 8cc271d226c..d5d565a015a 100644
--- a/crates/syntax/src/ast/token_ext.rs
+++ b/crates/syntax/src/ast/token_ext.rs
@@ -121,6 +121,7 @@ impl ast::Whitespace {
     }
 }
 
+#[derive(Debug)]
 pub struct QuoteOffsets {
     pub quotes: (TextRange, TextRange),
     pub contents: TextRange,
@@ -167,6 +168,11 @@ pub trait IsString: AstToken {
     fn text_range_between_quotes(&self) -> Option<TextRange> {
         self.quote_offsets().map(|it| it.contents)
     }
+    fn text_without_quotes(&self) -> &str {
+        let text = self.text();
+        let Some(offsets) = self.text_range_between_quotes() else { return text };
+        &text[offsets - self.syntax().text_range().start()]
+    }
     fn open_quote_text_range(&self) -> Option<TextRange> {
         self.quote_offsets().map(|it| it.quotes.0)
     }
diff --git a/crates/syntax/src/token_text.rs b/crates/syntax/src/token_text.rs
index 09c080c0c23..e69deb49ce1 100644
--- a/crates/syntax/src/token_text.rs
+++ b/crates/syntax/src/token_text.rs
@@ -13,7 +13,7 @@ pub(crate) enum Repr<'a> {
 }
 
 impl<'a> TokenText<'a> {
-    pub(crate) fn borrowed(text: &'a str) -> Self {
+    pub fn borrowed(text: &'a str) -> Self {
         TokenText(Repr::Borrowed(text))
     }
 
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index d39d62f3620..f766747d707 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -9,6 +9,8 @@
 //!
 //! Available flags:
 //!     add:
+//!     asm:
+//!     assert:
 //!     as_ref: sized
 //!     bool_impl: option, fn
 //!     builtin_impls:
@@ -1366,6 +1368,26 @@ mod macros {
     }
     // endregion:panic
 
+    // region:asm
+    #[macro_export]
+    #[rustc_builtin_macro]
+    macro_rules! asm {
+        ($($arg:tt)*) => {
+            /* compiler built-in */
+        };
+    }
+    // endregion:asm
+
+    // region:assert
+    #[macro_export]
+    #[rustc_builtin_macro]
+    macro_rules! assert {
+        ($($arg:tt)*) => {
+            /* compiler built-in */
+        };
+    }
+    // endregion:assert
+
     // region:fmt
     #[macro_export]
     #[rustc_builtin_macro]
@@ -1382,6 +1404,13 @@ mod macros {
     }
 
     #[macro_export]
+    #[rustc_builtin_macro]
+    macro_rules! format_args_nl {
+        ($fmt:expr) => {{ /* compiler built-in */ }};
+        ($fmt:expr, $($args:tt)*) => {{ /* compiler built-in */ }};
+    }
+
+    #[macro_export]
     macro_rules! print {
         ($($arg:tt)*) => {{
             $crate::io::_print($crate::format_args!($($arg)*));