Skip to content

Commit

Permalink
Render matched macro arm on hover of macro calls
Browse files Browse the repository at this point in the history
  • Loading branch information
Veykril committed Dec 8, 2023
1 parent 9e82ab5 commit 3105025
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 64 deletions.
44 changes: 25 additions & 19 deletions crates/hir-expand/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ impl DeclarativeMacroExpander {
db: &dyn ExpandDatabase,
tt: tt::Subtree,
call_id: MacroCallId,
) -> ExpandResult<tt::Subtree> {
) -> ExpandResult<(tt::Subtree, Option<u32>)> {
match self.mac.err() {
Some(e) => ExpandResult::new(
tt::Subtree::empty(tt::DelimSpan::DUMMY),
(tt::Subtree::empty(tt::DelimSpan::DUMMY), None),
ExpandError::other(format!("invalid macro definition: {e}")),
),
None => self
Expand All @@ -68,7 +68,7 @@ impl DeclarativeMacroExpander {
tt::Subtree::empty(tt::DelimSpan::DUMMY),
ExpandError::other(format!("invalid macro definition: {e}")),
),
None => self.mac.expand(&tt, |_| ()).map_err(Into::into),
None => self.mac.expand(&tt, |_| ()).map_err(Into::into).map(|(v, _)| v),
}
}
}
Expand Down Expand Up @@ -320,9 +320,11 @@ fn parse_macro_expansion(
let _p = profile::span("parse_macro_expansion");
let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
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(&tt, expand_to);
let (parse, mut rev_token_map) = token_tree_to_syntax_node(&tt, expand_to);
rev_token_map.matched_arm = matched_arm;

ExpandResult { value: (parse, Arc::new(rev_token_map)), err }
}
Expand Down Expand Up @@ -573,11 +575,12 @@ fn macro_expand(
db: &dyn ExpandDatabase,
macro_call_id: MacroCallId,
loc: MacroCallLoc,
) -> ExpandResult<Arc<tt::Subtree>> {
// FIXME: The Arc is no longer needed here
) -> ExpandResult<(Arc<tt::Subtree>, Option<u32>)> {
let _p = profile::span("macro_expand");

let ExpandResult { value: tt, mut err } = match loc.def.kind {
MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id),
let ExpandResult { value: (tt, matched_arm), mut err } = match loc.def.kind {
MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id).zip_val(None),
MacroDefKind::BuiltInDerive(expander, ..) => {
let (root, map) = parse_with_map(db, loc.kind.file_id());
let root = root.syntax_node();
Expand All @@ -586,16 +589,19 @@ fn macro_expand(

// FIXME: Use censoring
let _censor = censor_for_macro_input(&loc, node.syntax());
expander.expand(db, macro_call_id, &node, map.as_ref())
expander.expand(db, macro_call_id, &node, map.as_ref()).zip_val(None)
}
_ => {
let ValueResult { value, err } = db.macro_arg(macro_call_id);
let Some((macro_arg, undo_info)) = value else {
return ExpandResult {
value: Arc::new(tt::Subtree {
delimiter: tt::Delimiter::DUMMY_INVISIBLE,
token_trees: Vec::new(),
}),
value: (
Arc::new(tt::Subtree {
delimiter: tt::Delimiter::DUMMY_INVISIBLE,
token_trees: Vec::new(),
}),
None,
),
// FIXME: We should make sure to enforce an invariant that invalid macro
// calls do not reach this call path!
err: Some(ExpandError::other("invalid token tree")),
Expand All @@ -608,7 +614,7 @@ fn macro_expand(
db.decl_macro_expander(loc.def.krate, id).expand(db, arg.clone(), macro_call_id)
}
MacroDefKind::BuiltIn(it, _) => {
it.expand(db, macro_call_id, &arg).map_err(Into::into)
it.expand(db, macro_call_id, &arg).map_err(Into::into).zip_val(None)
}
// This might look a bit odd, but we do not expand the inputs to eager macros here.
// Eager macros inputs are expanded, well, eagerly when we collect the macro calls.
Expand All @@ -618,7 +624,7 @@ fn macro_expand(
// As such we just return the input subtree here.
MacroDefKind::BuiltInEager(..) if loc.eager.is_none() => {
return ExpandResult {
value: macro_arg.clone(),
value: (macro_arg.clone(), None),
err: err.map(|err| {
let mut buf = String::new();
for err in &**err {
Expand All @@ -632,12 +638,12 @@ fn macro_expand(
};
}
MacroDefKind::BuiltInEager(it, _) => {
it.expand(db, macro_call_id, &arg).map_err(Into::into)
it.expand(db, macro_call_id, &arg).map_err(Into::into).zip_val(None)
}
MacroDefKind::BuiltInAttr(it, _) => {
let mut res = it.expand(db, macro_call_id, &arg);
fixup::reverse_fixups(&mut res.value, &undo_info);
res
res.zip_val(None)
}
_ => unreachable!(),
}
Expand All @@ -653,11 +659,11 @@ 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;
return value.zip_val(matched_arm);
}
}

ExpandResult { value: Arc::new(tt), err }
ExpandResult { value: (Arc::new(tt), matched_arm), err }
}

fn expand_proc_macro(db: &dyn ExpandDatabase, id: MacroCallId) -> ExpandResult<Arc<tt::Subtree>> {
Expand Down
11 changes: 11 additions & 0 deletions crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,17 @@ impl<'db> SemanticsImpl<'db> {
sa.resolve_macro_call(self.db, macro_call)
}

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,
Expand Down
44 changes: 34 additions & 10 deletions crates/ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use ide_db::{
helpers::pick_best_token,
FxIndexSet, RootDatabase,
};
use itertools::Itertools;
use itertools::{multizip, Itertools};
use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};

use crate::{
Expand Down Expand Up @@ -146,7 +146,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))
});
}
Expand All @@ -159,6 +159,7 @@ fn hover_simple(
file_id,
Definition::from(resolution?),
&original_token.parent()?,
None,
config,
)?;
return Some(RangeInfo::new(range, res));
Expand Down Expand Up @@ -193,6 +194,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
Expand All @@ -201,20 +225,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)
.filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
.unique_by(|&(def, _, _)| def)
.filter_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));
Expand Down Expand Up @@ -312,13 +335,14 @@ pub(crate) fn hover_for_definition(
file_id: FileId,
definition: Definition,
scope_node: &SyntaxNode,
macro_arm: Option<u32>,
config: &HoverConfig,
) -> Option<HoverResult> {
let famous_defs = match &definition {
Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(scope_node)?.krate())),
_ => None,
};
render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
render::definition(sema.db, definition, famous_defs.as_ref(), macro_arm, config).map(|markup| {
HoverResult {
markup: render::process_markup(sema.db, definition, &markup, config),
actions: [
Expand Down
9 changes: 8 additions & 1 deletion crates/ide/src/hover/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,18 @@ pub(super) fn definition(
db: &RootDatabase,
def: Definition,
famous_defs: Option<&FamousDefs<'_, '_>>,
macro_arm: Option<u32>,
config: &HoverConfig,
) -> Option<Markup> {
let mod_path = definition_mod_path(db, &def);
let (label, docs) = match def {
Definition::Macro(it) => label_and_docs(db, it),
Definition::Macro(it) => {
let (mut label, docs) = label_and_docs(db, it);
if let Some(macro_arm) = macro_arm {
format_to!(label, " // matched arm #{}", macro_arm);
}
(label, docs)
}
Definition::Field(it) => label_and_layout_info_and_docs(
db,
it,
Expand Down
40 changes: 20 additions & 20 deletions crates/ide/src/hover/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1232,21 +1232,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
```
"#]],
)
}

Expand All @@ -1262,22 +1262,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
"#]],
)
}

Expand Down
2 changes: 1 addition & 1 deletion crates/ide/src/static_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ impl StaticIndex<'_> {
*it
} else {
let it = self.tokens.insert(TokenStaticData {
hover: hover_for_definition(&sema, file_id, def, &node, &hover_config),
hover: 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() }
}),
Expand Down
2 changes: 1 addition & 1 deletion crates/mbe/src/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn benchmark_expand_macro_rules() {
.map(|(id, tt)| {
let res = rules[&id].expand(&tt, |_| ());
assert!(res.err.is_none());
res.value.token_trees.len()
res.value.0.token_trees.len()
})
.sum()
};
Expand Down
20 changes: 10 additions & 10 deletions crates/mbe/src/expander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ pub(crate) fn expand_rules<S: Span>(
input: &tt::Subtree<S>,
marker: impl Fn(&mut S) + Copy,
is_2021: bool,
) -> ExpandResult<tt::Subtree<S>> {
let mut match_: Option<(matcher::Match<S>, &crate::Rule<S>)> = None;
for rule in rules {
) -> ExpandResult<(tt::Subtree<S>, Option<u32>)> {
let mut match_: Option<(matcher::Match<S>, &crate::Rule<S>, usize)> = None;
for (idx, rule) in rules.iter().enumerate() {
let new_match = matcher::match_(&rule.lhs, input, is_2021);

if new_match.err.is_none() {
Expand All @@ -28,28 +28,28 @@ pub(crate) fn expand_rules<S: Span>(
let ExpandResult { value, err: transcribe_err } =
transcriber::transcribe(&rule.rhs, &new_match.bindings, marker);
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);
ExpandResult { value, err: match_.err.or(transcribe_err) }
ExpandResult { value: (value, Some(idx as u32)), err: match_.err.or(transcribe_err) }
} else {
ExpandResult::new(
tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: vec![] },
(tt::Subtree { delimiter: tt::Delimiter::DUMMY_INVISIBLE, token_trees: vec![] }, None),
ExpandError::NoMatchingRule,
)
}
Expand Down

0 comments on commit 3105025

Please sign in to comment.