about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-expand/src/db.rs41
-rw-r--r--crates/hir-expand/src/declarative.rs6
-rw-r--r--crates/hir/src/semantics.rs11
-rw-r--r--crates/ide/src/hover.rs45
-rw-r--r--crates/ide/src/hover/render.rs8
-rw-r--r--crates/ide/src/hover/tests.rs40
-rw-r--r--crates/ide/src/static_index.rs9
-rw-r--r--crates/mbe/src/benchmark.rs2
-rw-r--r--crates/mbe/src/expander.rs31
-rw-r--r--crates/mbe/src/lib.rs9
-rw-r--r--crates/span/src/map.rs5
11 files changed, 140 insertions, 67 deletions
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index 97fa9cf2cc5..d7233a8923f 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -3,7 +3,7 @@
 use base_db::{salsa, CrateId, FileId, SourceDatabase};
 use either::Either;
 use limit::Limit;
-use mbe::syntax_node_to_token_tree;
+use mbe::{syntax_node_to_token_tree, MatchedArmIndex};
 use rustc_hash::FxHashSet;
 use span::{AstIdMap, Span, SyntaxContextData, SyntaxContextId};
 use syntax::{ast, AstNode, Parse, SyntaxElement, SyntaxError, SyntaxNode, SyntaxToken, T};
@@ -313,9 +313,10 @@ fn parse_macro_expansion(
     let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
     let edition = loc.def.edition;
     let expand_to = loc.expand_to();
-    let mbe::ValueResult { value: tt, err } = macro_expand(db, macro_file.macro_call_id, loc);
+    let mbe::ValueResult { value: (tt, matched_arm), err } =
+        macro_expand(db, macro_file.macro_call_id, loc);
 
-    let (parse, rev_token_map) = token_tree_to_syntax_node(
+    let (parse, mut rev_token_map) = token_tree_to_syntax_node(
         match &tt {
             CowArc::Arc(it) => it,
             CowArc::Owned(it) => it,
@@ -323,6 +324,7 @@ fn parse_macro_expansion(
         expand_to,
         edition,
     );
+    rev_token_map.matched_arm = matched_arm;
 
     ExpandResult { value: (parse, Arc::new(rev_token_map)), err }
 }
@@ -544,11 +546,13 @@ fn macro_expand(
     db: &dyn ExpandDatabase,
     macro_call_id: MacroCallId,
     loc: MacroCallLoc,
-) -> ExpandResult<CowArc<tt::Subtree>> {
+) -> ExpandResult<(CowArc<tt::Subtree>, MatchedArmIndex)> {
     let _p = tracing::span!(tracing::Level::INFO, "macro_expand").entered();
 
-    let (ExpandResult { value: tt, err }, span) = match loc.def.kind {
-        MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id).map(CowArc::Arc),
+    let (ExpandResult { value: (tt, matched_arm), err }, span) = match loc.def.kind {
+        MacroDefKind::ProcMacro(..) => {
+            return db.expand_proc_macro(macro_call_id).map(CowArc::Arc).zip_val(None)
+        }
         _ => {
             let (macro_arg, undo_info, span) =
                 db.macro_arg_considering_derives(macro_call_id, &loc.kind);
@@ -560,10 +564,10 @@ fn macro_expand(
                         .decl_macro_expander(loc.def.krate, id)
                         .expand(db, arg.clone(), macro_call_id, span),
                     MacroDefKind::BuiltIn(it, _) => {
-                        it.expand(db, macro_call_id, arg, span).map_err(Into::into)
+                        it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
                     }
                     MacroDefKind::BuiltInDerive(it, _) => {
-                        it.expand(db, macro_call_id, arg, span).map_err(Into::into)
+                        it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
                     }
                     MacroDefKind::BuiltInEager(it, _) => {
                         // This might look a bit odd, but we do not expand the inputs to eager macros here.
@@ -574,7 +578,8 @@ fn macro_expand(
                         // As such we just return the input subtree here.
                         let eager = match &loc.kind {
                             MacroCallKind::FnLike { eager: None, .. } => {
-                                return ExpandResult::ok(CowArc::Arc(macro_arg.clone()));
+                                return ExpandResult::ok(CowArc::Arc(macro_arg.clone()))
+                                    .zip_val(None);
                             }
                             MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager),
                             _ => None,
@@ -586,12 +591,12 @@ fn macro_expand(
                             // FIXME: We should report both errors!
                             res.err = error.clone().or(res.err);
                         }
-                        res
+                        res.zip_val(None)
                     }
                     MacroDefKind::BuiltInAttr(it, _) => {
                         let mut res = it.expand(db, macro_call_id, arg, span);
                         fixup::reverse_fixups(&mut res.value, &undo_info);
-                        res
+                        res.zip_val(None)
                     }
                     _ => unreachable!(),
                 };
@@ -603,16 +608,18 @@ fn macro_expand(
     if !loc.def.is_include() {
         // Set a hard limit for the expanded tt
         if let Err(value) = check_tt_count(&tt) {
-            return value.map(|()| {
-                CowArc::Owned(tt::Subtree {
-                    delimiter: tt::Delimiter::invisible_spanned(span),
-                    token_trees: Box::new([]),
+            return value
+                .map(|()| {
+                    CowArc::Owned(tt::Subtree {
+                        delimiter: tt::Delimiter::invisible_spanned(span),
+                        token_trees: Box::new([]),
+                    })
                 })
-            });
+                .zip_val(matched_arm);
         }
     }
 
-    ExpandResult { value: CowArc::Owned(tt), err }
+    ExpandResult { value: (CowArc::Owned(tt), matched_arm), err }
 }
 
 fn proc_macro_span(db: &dyn ExpandDatabase, ast: AstId<ast::Fn>) -> Span {
diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs
index f9ea8e2ea53..66465ce6005 100644
--- a/crates/hir-expand/src/declarative.rs
+++ b/crates/hir-expand/src/declarative.rs
@@ -3,6 +3,7 @@ use std::sync::OnceLock;
 
 use base_db::{CrateId, VersionReq};
 use span::{Edition, MacroCallId, Span, SyntaxContextId};
+use stdx::TupleExt;
 use syntax::{ast, AstNode};
 use triomphe::Arc;
 
@@ -30,7 +31,7 @@ impl DeclarativeMacroExpander {
         tt: tt::Subtree,
         call_id: MacroCallId,
         span: Span,
-    ) -> ExpandResult<tt::Subtree> {
+    ) -> ExpandResult<(tt::Subtree, Option<u32>)> {
         let loc = db.lookup_intern_macro_call(call_id);
         let toolchain = db.toolchain(loc.def.krate);
         let new_meta_vars = toolchain.as_ref().map_or(false, |version| {
@@ -46,7 +47,7 @@ impl DeclarativeMacroExpander {
         });
         match self.mac.err() {
             Some(_) => ExpandResult::new(
-                tt::Subtree::empty(tt::DelimSpan { open: span, close: span }),
+                (tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), None),
                 ExpandError::MacroDefinition,
             ),
             None => self
@@ -90,6 +91,7 @@ impl DeclarativeMacroExpander {
             None => self
                 .mac
                 .expand(&tt, |_| (), new_meta_vars, call_site, def_site_edition)
+                .map(TupleExt::head)
                 .map_err(Into::into),
         }
     }
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index e1516fcdcbe..e792e159acf 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1246,6 +1246,17 @@ impl<'db> SemanticsImpl<'db> {
             .map_or(false, |m| matches!(m.id, MacroId::ProcMacroId(..)))
     }
 
+    pub fn resolve_macro_call_arm(&self, macro_call: &ast::MacroCall) -> Option<u32> {
+        let sa = self.analyze(macro_call.syntax())?;
+        self.db
+            .parse_macro_expansion(
+                sa.expand(self.db, self.wrap_node_infile(macro_call.clone()).as_ref())?,
+            )
+            .value
+            .1
+            .matched_arm
+    }
+
     pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
         let sa = match self.analyze(macro_call.syntax()) {
             Some(it) => it,
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 822751c0e4c..95de3c88c8a 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use ide_db::{
     helpers::pick_best_token,
     FxIndexSet, RootDatabase,
 };
-use itertools::Itertools;
+use itertools::{multizip, Itertools};
 use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T};
 
 use crate::{
@@ -149,7 +149,7 @@ fn hover_simple(
     if let Some(doc_comment) = token_as_doc_comment(&original_token) {
         cov_mark::hit!(no_highlight_on_comment_hover);
         return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
-            let res = hover_for_definition(sema, file_id, def, &node, config);
+            let res = hover_for_definition(sema, file_id, def, &node, None, config);
             Some(RangeInfo::new(range, res))
         });
     }
@@ -162,6 +162,7 @@ fn hover_simple(
             file_id,
             Definition::from(resolution?),
             &original_token.parent()?,
+            None,
             config,
         );
         return Some(RangeInfo::new(range, res));
@@ -196,6 +197,29 @@ fn hover_simple(
             descended()
                 .filter_map(|token| {
                     let node = token.parent()?;
+
+                    // special case macro calls, we wanna render the invoked arm index
+                    if let Some(name) = ast::NameRef::cast(node.clone()) {
+                        if let Some(path_seg) =
+                            name.syntax().parent().and_then(ast::PathSegment::cast)
+                        {
+                            if let Some(macro_call) = path_seg
+                                .parent_path()
+                                .syntax()
+                                .parent()
+                                .and_then(ast::MacroCall::cast)
+                            {
+                                if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
+                                    return Some(vec![(
+                                        Definition::Macro(macro_),
+                                        sema.resolve_macro_call_arm(&macro_call),
+                                        node,
+                                    )]);
+                                }
+                            }
+                        }
+                    }
+
                     match IdentClass::classify_node(sema, &node)? {
                         // It's better for us to fall back to the keyword hover here,
                         // rendering poll is very confusing
@@ -204,20 +228,19 @@ fn hover_simple(
                         IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
                             decl,
                             ..
-                        }) => Some(vec![(Definition::ExternCrateDecl(decl), node)]),
+                        }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
 
                         class => Some(
-                            class
-                                .definitions()
-                                .into_iter()
-                                .zip(iter::repeat(node))
+                            multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
                                 .collect::<Vec<_>>(),
                         ),
                     }
                 })
                 .flatten()
-                .unique_by(|&(def, _)| def)
-                .map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+                .unique_by(|&(def, _, _)| def)
+                .map(|(def, macro_arm, node)| {
+                    hover_for_definition(sema, file_id, def, &node, macro_arm, config)
+                })
                 .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
                     acc.actions.extend(actions);
                     acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
@@ -374,6 +397,7 @@ pub(crate) fn hover_for_definition(
     file_id: FileId,
     def: Definition,
     scope_node: &SyntaxNode,
+    macro_arm: Option<u32>,
     config: &HoverConfig,
 ) -> HoverResult {
     let famous_defs = match &def {
@@ -398,7 +422,8 @@ pub(crate) fn hover_for_definition(
     };
     let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
 
-    let markup = render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, config);
+    let markup =
+        render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, macro_arm, config);
     HoverResult {
         markup: render::process_markup(sema.db, def, &markup, config),
         actions: [
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 7579d4d6d8b..3f0fc851344 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -403,6 +403,7 @@ pub(super) fn definition(
     def: Definition,
     famous_defs: Option<&FamousDefs<'_, '_>>,
     notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
+    macro_arm: Option<u32>,
     config: &HoverConfig,
 ) -> Markup {
     let mod_path = definition_mod_path(db, &def);
@@ -413,6 +414,13 @@ pub(super) fn definition(
         Definition::Adt(Adt::Struct(struct_)) => {
             struct_.display_limited(db, config.max_struct_field_count).to_string()
         }
+        Definition::Macro(it) => {
+            let mut label = it.display(db).to_string();
+            if let Some(macro_arm) = macro_arm {
+                format_to!(label, " // matched arm #{}", macro_arm);
+            }
+            label
+        }
         _ => def.label(db),
     };
     let docs = def.docs(db, famous_defs);
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 67f10f0374d..6bbc8b380d6 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -1560,21 +1560,21 @@ fn y() {
 fn test_hover_macro_invocation() {
     check(
         r#"
-macro_rules! foo { () => {} }
+macro_rules! foo { (a) => {}; () => {} }
 
 fn f() { fo$0o!(); }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                macro_rules! foo
-                ```
-            "#]],
+            ```rust
+            macro_rules! foo // matched arm #1
+            ```
+        "#]],
     )
 }
 
@@ -1590,22 +1590,22 @@ macro foo() {}
 fn f() { fo$0o!(); }
 "#,
         expect![[r#"
-                *foo*
+            *foo*
 
-                ```rust
-                test
-                ```
+            ```rust
+            test
+            ```
 
-                ```rust
-                macro foo
-                ```
+            ```rust
+            macro foo // matched arm #0
+            ```
 
-                ---
+            ---
 
-                foo bar
+            foo bar
 
-                foo bar baz
-            "#]],
+            foo bar baz
+        "#]],
     )
 }
 
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 3fef16df25e..ca013da7099 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -188,7 +188,14 @@ impl StaticIndex<'_> {
             } else {
                 let it = self.tokens.insert(TokenStaticData {
                     documentation: documentation_for_definition(&sema, def, &node),
-                    hover: Some(hover_for_definition(&sema, file_id, def, &node, &hover_config)),
+                    hover: Some(hover_for_definition(
+                        &sema,
+                        file_id,
+                        def,
+                        &node,
+                        None,
+                        &hover_config,
+                    )),
                     definition: def.try_to_nav(self.db).map(UpmappingResult::call_site).map(|it| {
                         FileRange { file_id: it.file_id, range: it.focus_or_full_range() }
                     }),
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index 1dca288017f..f4bbaef7af3 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -48,7 +48,7 @@ fn benchmark_expand_macro_rules() {
             .map(|(id, tt)| {
                 let res = rules[&id].expand(&tt, |_| (), true, DUMMY, Edition::CURRENT);
                 assert!(res.err.is_none());
-                res.value.token_trees.len()
+                res.value.0.token_trees.len()
             })
             .sum()
     };
diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs
index 2d495da0dbc..cfad8bcc0b4 100644
--- a/crates/mbe/src/expander.rs
+++ b/crates/mbe/src/expander.rs
@@ -9,7 +9,7 @@ use rustc_hash::FxHashMap;
 use span::{Edition, Span};
 use syntax::SmolStr;
 
-use crate::{parser::MetaVarKind, ExpandError, ExpandResult};
+use crate::{parser::MetaVarKind, ExpandError, ExpandResult, MatchedArmIndex};
 
 pub(crate) fn expand_rules(
     rules: &[crate::Rule],
@@ -18,9 +18,9 @@ pub(crate) fn expand_rules(
     new_meta_vars: bool,
     call_site: Span,
     def_site_edition: Edition,
-) -> ExpandResult<tt::Subtree<Span>> {
-    let mut match_: Option<(matcher::Match, &crate::Rule)> = None;
-    for rule in rules {
+) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
+    let mut match_: Option<(matcher::Match, &crate::Rule, usize)> = None;
+    for (idx, rule) in rules.iter().enumerate() {
         let new_match = matcher::match_(&rule.lhs, input, def_site_edition);
 
         if new_match.err.is_none() {
@@ -35,31 +35,34 @@ pub(crate) fn expand_rules(
                 call_site,
             );
             if transcribe_err.is_none() {
-                return ExpandResult::ok(value);
+                return ExpandResult::ok((value, Some(idx as u32)));
             }
         }
         // Use the rule if we matched more tokens, or bound variables count
-        if let Some((prev_match, _)) = &match_ {
+        if let Some((prev_match, _, _)) = &match_ {
             if (new_match.unmatched_tts, -(new_match.bound_count as i32))
                 < (prev_match.unmatched_tts, -(prev_match.bound_count as i32))
             {
-                match_ = Some((new_match, rule));
+                match_ = Some((new_match, rule, idx));
             }
         } else {
-            match_ = Some((new_match, rule));
+            match_ = Some((new_match, rule, idx));
         }
     }
-    if let Some((match_, rule)) = match_ {
+    if let Some((match_, rule, idx)) = match_ {
         // if we got here, there was no match without errors
         let ExpandResult { value, err: transcribe_err } =
             transcriber::transcribe(&rule.rhs, &match_.bindings, marker, new_meta_vars, call_site);
-        ExpandResult { value, err: match_.err.or(transcribe_err) }
+        ExpandResult { value: (value, idx.try_into().ok()), err: match_.err.or(transcribe_err) }
     } else {
         ExpandResult::new(
-            tt::Subtree {
-                delimiter: tt::Delimiter::invisible_spanned(call_site),
-                token_trees: Box::new([]),
-            },
+            (
+                tt::Subtree {
+                    delimiter: tt::Delimiter::invisible_spanned(call_site),
+                    token_trees: Box::default(),
+                },
+                None,
+            ),
             ExpandError::NoMatchingRule,
         )
     }
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index 5445f8790fc..d5de56312a3 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -122,6 +122,9 @@ impl fmt::Display for CountError {
     }
 }
 
+/// Index of the matched macro arm on successful expansion.
+pub type MatchedArmIndex = Option<u32>;
+
 /// This struct contains AST for a single `macro_rules` definition. What might
 /// be very confusing is that AST has almost exactly the same shape as
 /// `tt::TokenTree`, but there's a crucial difference: in macro rules, `$ident`
@@ -251,7 +254,7 @@ impl DeclarativeMacro {
         new_meta_vars: bool,
         call_site: Span,
         def_site_edition: Edition,
-    ) -> ExpandResult<tt::Subtree<Span>> {
+    ) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
         expander::expand_rules(&self.rules, tt, marker, new_meta_vars, call_site, def_site_edition)
     }
 }
@@ -330,6 +333,10 @@ impl<T, E> ValueResult<T, E> {
         Self { value: Default::default(), err: Some(err) }
     }
 
+    pub fn zip_val<U>(self, other: U) -> ValueResult<(T, U), E> {
+        ValueResult { value: (self.value, other), err: self.err }
+    }
+
     pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ValueResult<U, E> {
         ValueResult { value: f(self.value), err: self.err }
     }
diff --git a/crates/span/src/map.rs b/crates/span/src/map.rs
index 6d8c9c30fb5..81fc56c961e 100644
--- a/crates/span/src/map.rs
+++ b/crates/span/src/map.rs
@@ -15,6 +15,9 @@ use crate::{
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub struct SpanMap<S> {
     spans: Vec<(TextSize, SpanData<S>)>,
+    /// Index of the matched macro arm on successful expansion for declarative macros.
+    // FIXME: Does it make sense to have this here?
+    pub matched_arm: Option<u32>,
 }
 
 impl<S> SpanMap<S>
@@ -23,7 +26,7 @@ where
 {
     /// Creates a new empty [`SpanMap`].
     pub fn empty() -> Self {
-        Self { spans: Vec::new() }
+        Self { spans: Vec::new(), matched_arm: None }
     }
 
     /// Finalizes the [`SpanMap`], shrinking its backing storage and validating that the offsets are