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:
commit
08c706f2f8
@ -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 {
|
||||
|
@ -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()
|
||||
};
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user