Auto merge of #16057 - Veykril:macro-arm, r=Veykril

Render matched macro arm on hover of macro calls

Fixes https://github.com/rust-lang/rust-analyzer/issues/4028, its a different take on the idea. I don't like go to being changed here simply because we can't point the focus range on the name anymore as we usually do, and some editors might use this feature (and the focus range) for certain other things. We could instead add a new hover action for this to move to the arm directly (or maybe make `go to implementation` jump to the arm?)
This commit is contained in:
bors 2024-04-18 09:02:39 +00:00
commit 08c706f2f8
11 changed files with 140 additions and 67 deletions

View File

@ -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 {

View File

@ -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),
}
}

View File

@ -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,

View File

@ -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: [

View File

@ -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);

View File

@ -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
"#]],
)
}

View File

@ -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() }
}),

View File

@ -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()
};

View File

@ -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,
)
}

View File

@ -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 }
}

View File

@ -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