Skip to content

Commit 4c9eef7

Browse files
bors[bot]Anatol Ulrich
andauthored
Merge #10233
10233: fix: add multi-token mapping support to hovers r=Veykril a=spookyvision implement #10070 in [`hover`](https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ide/src/hover.rs) Co-authored-by: Anatol Ulrich <[email protected]>
2 parents 67706cd + 45090e4 commit 4c9eef7

File tree

1 file changed

+175
-21
lines changed

1 file changed

+175
-21
lines changed

crates/ide/src/hover.rs

Lines changed: 175 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::{collections::HashSet, ops::ControlFlow};
2+
13
use either::Either;
24
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
35
use ide_db::{
@@ -13,7 +15,7 @@ use itertools::Itertools;
1315
use stdx::format_to;
1416
use syntax::{
1517
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, Direction, SyntaxKind::*,
16-
SyntaxNode, SyntaxToken, T,
18+
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
1719
};
1820

1921
use crate::{
@@ -111,16 +113,69 @@ pub(crate) fn hover(
111113
kind if kind.is_trivia() => 0,
112114
_ => 1,
113115
})?;
114-
let token = sema.descend_into_macros(token);
115-
let node = token.parent()?;
116+
117+
let mut seen = HashSet::default();
118+
119+
let mut fallback = None;
120+
sema.descend_into_macros_many(token.clone())
121+
.iter()
122+
.filter_map(|token| match token.parent() {
123+
Some(node) => {
124+
match find_hover_result(&sema, file_id, offset, config, token, &node, &mut seen) {
125+
Some(res) => match res {
126+
ControlFlow::Break(inner) => Some(inner),
127+
ControlFlow::Continue(_) => {
128+
if fallback.is_none() {
129+
// FIXME we're only taking the first fallback into account that's not `None`
130+
fallback = hover_for_keyword(&sema, config, &token)
131+
.or(type_hover(&sema, config, &token));
132+
}
133+
None
134+
}
135+
},
136+
None => None,
137+
}
138+
}
139+
None => None,
140+
})
141+
// reduce all descends into a single `RangeInfo`
142+
// that spans from the earliest start to the latest end (fishy/FIXME),
143+
// concatenates all `Markup`s with `\n---\n`,
144+
// and accumulates all actions into its `actions` vector.
145+
.reduce(|mut acc, RangeInfo { range, mut info }| {
146+
let start = acc.range.start().min(range.start());
147+
let end = acc.range.end().max(range.end());
148+
149+
acc.range = TextRange::new(start, end);
150+
acc.info.actions.append(&mut info.actions);
151+
acc.info.markup = Markup::from(format!("{}\n---\n{}", acc.info.markup, info.markup));
152+
acc
153+
})
154+
.or(fallback)
155+
}
156+
157+
fn find_hover_result(
158+
sema: &Semantics<RootDatabase>,
159+
file_id: FileId,
160+
offset: TextSize,
161+
config: &HoverConfig,
162+
token: &SyntaxToken,
163+
node: &SyntaxNode,
164+
seen: &mut HashSet<Definition>,
165+
) -> Option<ControlFlow<RangeInfo<HoverResult>>> {
116166
let mut range_override = None;
167+
168+
// intra-doc links and attributes are special cased
169+
// so don't add them to the `seen` duplicate check
170+
let mut add_to_seen_definitions = true;
171+
117172
let definition = match_ast! {
118173
match node {
119-
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
174+
ast::Name(name) => NameClass::classify(sema, &name).map(|class| match class {
120175
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
121176
NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
122177
}),
123-
ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|class| match class {
178+
ast::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref).map(|class| match class {
124179
NameRefClass::Definition(def) => def,
125180
NameRefClass::FieldShorthand { local_ref: _, field_ref } => {
126181
Definition::Field(field_ref)
@@ -137,25 +192,37 @@ pub(crate) fn hover(
137192
),
138193
_ => {
139194
// intra-doc links
195+
// FIXME: move comment + attribute special cases somewhere else to simplify control flow,
196+
// hopefully simplifying the return type of this function in the process
197+
// (the `Break`/`Continue` distinction is needed to decide whether to use fallback hovers)
198+
//
199+
// FIXME: hovering the intra doc link to `Foo` not working:
200+
//
201+
// #[identity]
202+
// trait Foo {
203+
// /// [`Foo`]
204+
// fn foo() {}
140205
if token.kind() == COMMENT {
206+
add_to_seen_definitions = false;
141207
cov_mark::hit!(no_highlight_on_comment_hover);
142-
let (attributes, def) = doc_attributes(&sema, &node)?;
143-
let (docs, doc_mapping) = attributes.docs_with_rangemap(db)?;
208+
let (attributes, def) = doc_attributes(sema, node)?;
209+
let (docs, doc_mapping) = attributes.docs_with_rangemap(sema.db)?;
144210
let (idl_range, link, ns) =
145211
extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
146212
let mapped = doc_mapping.map(range)?;
147213
(mapped.file_id == file_id.into() && mapped.value.contains(offset)).then(||(mapped.value, link, ns))
148214
})?;
149215
range_override = Some(idl_range);
150-
Some(match resolve_doc_path_for_def(db,def, &link,ns)? {
216+
Some(match resolve_doc_path_for_def(sema.db,def, &link,ns)? {
151217
Either::Left(it) => Definition::ModuleDef(it),
152218
Either::Right(it) => Definition::Macro(it),
153219
})
154220
// attributes, require special machinery as they are mere ident tokens
155221
} else if let Some(attr) = token.ancestors().find_map(ast::Attr::cast) {
222+
add_to_seen_definitions = false;
156223
// lints
157-
if let res@Some(_) = try_hover_for_lint(&attr, &token) {
158-
return res;
224+
if let Some(res) = try_hover_for_lint(&attr, &token) {
225+
return Some(ControlFlow::Break(res));
159226
// derives
160227
} else {
161228
range_override = Some(token.text_range());
@@ -169,42 +236,53 @@ pub(crate) fn hover(
169236
};
170237

171238
if let Some(definition) = definition {
239+
// skip duplicates
240+
if seen.contains(&definition) {
241+
return None;
242+
}
243+
if add_to_seen_definitions {
244+
seen.insert(definition);
245+
}
172246
let famous_defs = match &definition {
173247
Definition::ModuleDef(hir::ModuleDef::BuiltinType(_)) => {
174248
Some(FamousDefs(&sema, sema.scope(&node).krate()))
175249
}
176250
_ => None,
177251
};
178-
if let Some(markup) = hover_for_definition(db, definition, famous_defs.as_ref(), config) {
252+
if let Some(markup) =
253+
hover_for_definition(sema.db, definition, famous_defs.as_ref(), config)
254+
{
179255
let mut res = HoverResult::default();
180256
res.markup = process_markup(sema.db, definition, &markup, config);
181-
if let Some(action) = show_implementations_action(db, definition) {
257+
if let Some(action) = show_implementations_action(sema.db, definition) {
182258
res.actions.push(action);
183259
}
184260

185-
if let Some(action) = show_fn_references_action(db, definition) {
261+
if let Some(action) = show_fn_references_action(sema.db, definition) {
186262
res.actions.push(action);
187263
}
188264

189265
if let Some(action) = runnable_action(&sema, definition, file_id) {
190266
res.actions.push(action);
191267
}
192268

193-
if let Some(action) = goto_type_action_for_def(db, definition) {
269+
if let Some(action) = goto_type_action_for_def(sema.db, definition) {
194270
res.actions.push(action);
195271
}
196272

197273
let range = range_override.unwrap_or_else(|| sema.original_range(&node).range);
198-
return Some(RangeInfo::new(range, res));
274+
return Some(ControlFlow::Break(RangeInfo::new(range, res)));
199275
}
200276
}
201277

202-
if let res @ Some(_) = hover_for_keyword(&sema, config, &token) {
203-
return res;
204-
}
205-
206-
// No definition below cursor, fall back to showing type hovers.
278+
Some(ControlFlow::Continue(()))
279+
}
207280

281+
fn type_hover(
282+
sema: &Semantics<RootDatabase>,
283+
config: &HoverConfig,
284+
token: &SyntaxToken,
285+
) -> Option<RangeInfo<HoverResult>> {
208286
let node = token
209287
.ancestors()
210288
.take_while(|it| !ast::Item::can_cast(it.kind()))
@@ -214,7 +292,7 @@ pub(crate) fn hover(
214292
match node {
215293
ast::Expr(it) => Either::Left(it),
216294
ast::Pat(it) => Either::Right(it),
217-
// If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve.
295+
// If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
218296
// (e.g expanding a builtin macro). So we give up here.
219297
ast::MacroCall(_it) => return None,
220298
_ => return None,
@@ -914,6 +992,82 @@ mod tests {
914992
assert!(hover.is_none());
915993
}
916994

995+
#[test]
996+
fn hover_descend_macros_avoids_duplicates() {
997+
check(
998+
r#"
999+
macro_rules! dupe_use {
1000+
($local:ident) => {
1001+
{
1002+
$local;
1003+
$local;
1004+
}
1005+
}
1006+
}
1007+
fn foo() {
1008+
let local = 0;
1009+
dupe_use!(local$0);
1010+
}
1011+
"#,
1012+
expect![[r#"
1013+
*local*
1014+
1015+
```rust
1016+
let local: i32
1017+
```
1018+
"#]],
1019+
);
1020+
}
1021+
1022+
#[test]
1023+
fn hover_shows_all_macro_descends() {
1024+
check(
1025+
r#"
1026+
macro_rules! m {
1027+
($name:ident) => {
1028+
/// Outer
1029+
fn $name() {}
1030+
1031+
mod module {
1032+
/// Inner
1033+
fn $name() {}
1034+
}
1035+
};
1036+
}
1037+
1038+
m!(ab$0c);
1039+
"#,
1040+
expect![[r#"
1041+
*abc*
1042+
1043+
```rust
1044+
test::module
1045+
```
1046+
1047+
```rust
1048+
fn abc()
1049+
```
1050+
1051+
---
1052+
1053+
Inner
1054+
---
1055+
1056+
```rust
1057+
test
1058+
```
1059+
1060+
```rust
1061+
fn abc()
1062+
```
1063+
1064+
---
1065+
1066+
Outer
1067+
"#]],
1068+
);
1069+
}
1070+
9171071
#[test]
9181072
fn hover_shows_type_of_an_expression() {
9191073
check(

0 commit comments

Comments
 (0)