From 6bfdd38c694ccfed34e4215a44df8b7a72daac76 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 8 Dec 2023 18:24:24 +0100 Subject: [PATCH] Render matched macro arm on hover of macro calls --- crates/hir-expand/src/db.rs | 39 ++++++++++++++---------- crates/hir-expand/src/declarative.rs | 6 ++-- crates/hir/src/semantics.rs | 11 +++++++ crates/ide/src/hover.rs | 45 +++++++++++++++++++++------- crates/ide/src/hover/render.rs | 8 +++++ crates/ide/src/hover/tests.rs | 40 ++++++++++++------------- crates/ide/src/static_index.rs | 9 +++++- crates/mbe/src/benchmark.rs | 2 +- crates/mbe/src/expander.rs | 29 ++++++++++-------- crates/mbe/src/lib.rs | 6 +++- crates/span/src/map.rs | 3 +- 11 files changed, 133 insertions(+), 65 deletions(-) diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 97fa9cf2cc..33c0373e48 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -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> { +) -> ExpandResult<(CowArc, Option)> { 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) -> Span { diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs index f9ea8e2ea5..66465ce600 100644 --- a/crates/hir-expand/src/declarative.rs +++ b/crates/hir-expand/src/declarative.rs @@ -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 { + ) -> ExpandResult<(tt::Subtree, Option)> { 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), } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index e1516fcdcb..e792e159ac 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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 { + 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, diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 822751c0e4..95de3c88c8 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -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::>(), ), } }) .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, 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: [ diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 7579d4d6d8..3f0fc85134 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -403,6 +403,7 @@ pub(super) fn definition( def: Definition, famous_defs: Option<&FamousDefs<'_, '_>>, notable_traits: &[(Trait, Vec<(Option, Name)>)], + macro_arm: Option, 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); diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index 67f10f0374..6bbc8b380d 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -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 + "#]], ) } diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs index 3fef16df25..ca013da709 100644 --- a/crates/ide/src/static_index.rs +++ b/crates/ide/src/static_index.rs @@ -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() } }), diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs index 1dca288017..f4bbaef7af 100644 --- a/crates/mbe/src/benchmark.rs +++ b/crates/mbe/src/benchmark.rs @@ -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() }; diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs index 2d495da0db..b1468e5bef 100644 --- a/crates/mbe/src/expander.rs +++ b/crates/mbe/src/expander.rs @@ -18,9 +18,9 @@ pub(crate) fn expand_rules( new_meta_vars: bool, call_site: Span, def_site_edition: Edition, -) -> ExpandResult> { - let mut match_: Option<(matcher::Match, &crate::Rule)> = None; - for rule in rules { +) -> ExpandResult<(tt::Subtree, Option)> { + 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, ) } diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs index 5445f8790f..77e6490317 100644 --- a/crates/mbe/src/lib.rs +++ b/crates/mbe/src/lib.rs @@ -251,7 +251,7 @@ impl DeclarativeMacro { new_meta_vars: bool, call_site: Span, def_site_edition: Edition, - ) -> ExpandResult> { + ) -> ExpandResult<(tt::Subtree, Option)> { expander::expand_rules(&self.rules, tt, marker, new_meta_vars, call_site, def_site_edition) } } @@ -330,6 +330,10 @@ impl ValueResult { Self { value: Default::default(), err: Some(err) } } + pub fn zip_val(self, other: U) -> ValueResult<(T, U), E> { + ValueResult { value: (self.value, other), err: self.err } + } + pub fn map(self, f: impl FnOnce(T) -> U) -> ValueResult { ValueResult { value: f(self.value), err: self.err } } diff --git a/crates/span/src/map.rs b/crates/span/src/map.rs index 6d8c9c30fb..e769b6420e 100644 --- a/crates/span/src/map.rs +++ b/crates/span/src/map.rs @@ -15,6 +15,7 @@ use crate::{ #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct SpanMap { spans: Vec<(TextSize, SpanData)>, + pub matched_arm: Option, } impl SpanMap @@ -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