mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 04:53:34 +00:00
Render matched macro arm on hover of macro calls
This commit is contained in:
parent
062e1b9b81
commit
6bfdd38c69
11 changed files with 133 additions and 65 deletions
|
@ -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>, Option<u32>)> {
|
||||
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 {
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(¯o_call) {
|
||||
return Some(vec![(
|
||||
Definition::Macro(macro_),
|
||||
sema.resolve_macro_call_arm(¯o_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(), ¬able_traits, config);
|
||||
let markup =
|
||||
render::definition(sema.db, def, famous_defs.as_ref(), ¬able_traits, macro_arm, config);
|
||||
HoverResult {
|
||||
markup: render::process_markup(sema.db, def, &markup, config),
|
||||
actions: [
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
"#]],
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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() }
|
||||
}),
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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>, Option<u32>)> {
|
||||
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, Some(idx as u32)), 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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ impl DeclarativeMacro {
|
|||
new_meta_vars: bool,
|
||||
call_site: Span,
|
||||
def_site_edition: Edition,
|
||||
) -> ExpandResult<tt::Subtree<Span>> {
|
||||
) -> ExpandResult<(tt::Subtree<Span>, Option<u32>)> {
|
||||
expander::expand_rules(&self.rules, tt, marker, new_meta_vars, call_site, def_site_edition)
|
||||
}
|
||||
}
|
||||
|
@ -330,6 +330,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 }
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ use crate::{
|
|||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct SpanMap<S> {
|
||||
spans: Vec<(TextSize, SpanData<S>)>,
|
||||
pub matched_arm: Option<u32>,
|
||||
}
|
||||
|
||||
impl<S> SpanMap<S>
|
||||
|
@ -23,7 +24,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
|
||||
|
|
Loading…
Reference in a new issue