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
11 changed files with 140 additions and 67 deletions
+24 -17
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 {
+4 -2
View File
@@ -3,6 +3,7 @@
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 @@ pub fn expand(
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 @@ pub fn expand(
});
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 @@ pub fn expand_unhygienic(
None => self
.mac
.expand(&tt, |_| (), new_meta_vars, call_site, def_site_edition)
.map(TupleExt::head)
.map_err(Into::into),
}
}
+11
View File
@@ -1246,6 +1246,17 @@ pub fn is_proc_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
.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,
+35 -10
View File
@@ -14,7 +14,7 @@
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: [
+8
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);
+20 -20
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 @@ fn test_hover_macro2_invocation() {
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
"#]],
)
}
+8 -1
View File
@@ -188,7 +188,14 @@ fn add_file(&mut self, file_id: FileId) {
} 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() }
}),
+1 -1
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()
};
+17 -14
View File
@@ -9,7 +9,7 @@
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,
)
}
+8 -1
View File
@@ -122,6 +122,9 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
}
/// 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 @@ pub fn expand(
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 @@ pub fn only_err(err: E) -> Self
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 }
}
+4 -1
View File
@@ -15,6 +15,9 @@
#[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 @@ impl<S> SpanMap<S>
{
/// 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