Skip to content

Commit 303737d

Browse files
committed
Auto merge of rust-lang#13989 - Veykril:hover, r=Veykril
internal: Remove hover fallback in favor of ranged hover The fallback is usually more annoying than useful at this point (it messes with the range of diagnostic popups a lot), we now have a ranged hover to check the type of something which works a lot better. Closes rust-lang/rust-analyzer#11602
2 parents ce67dea + 4685b97 commit 303737d

File tree

3 files changed

+235
-189
lines changed

3 files changed

+235
-189
lines changed

crates/ide/src/hover.rs

Lines changed: 63 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ide_db::{
1515
FxIndexSet, RootDatabase,
1616
};
1717
use itertools::Itertools;
18-
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};
18+
use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
1919

2020
use crate::{
2121
doc_links::token_as_doc_comment,
@@ -86,30 +86,38 @@ pub struct HoverResult {
8686
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
8787
pub(crate) fn hover(
8888
db: &RootDatabase,
89-
file_range: FileRange,
89+
frange @ FileRange { file_id, range }: FileRange,
9090
config: &HoverConfig,
9191
) -> Option<RangeInfo<HoverResult>> {
9292
let sema = &hir::Semantics::new(db);
93-
let mut res = hover_impl(sema, file_range, config)?;
93+
let file = sema.parse(file_id).syntax().clone();
94+
let mut res = if range.is_empty() {
95+
hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
96+
} else {
97+
hover_ranged(sema, frange, file, config)
98+
}?;
99+
94100
if let HoverDocFormat::PlainText = config.format {
95101
res.info.markup = remove_markdown(res.info.markup.as_str()).into();
96102
}
97103
Some(res)
98104
}
99105

100-
fn hover_impl(
106+
fn hover_simple(
101107
sema: &Semantics<'_, RootDatabase>,
102-
FileRange { file_id, range }: FileRange,
108+
FilePosition { file_id, offset }: FilePosition,
109+
file: SyntaxNode,
103110
config: &HoverConfig,
104111
) -> Option<RangeInfo<HoverResult>> {
105-
let file = sema.parse(file_id).syntax().clone();
106-
if !range.is_empty() {
107-
return hover_ranged(&file, range, sema, config);
108-
}
109-
let offset = range.start();
110-
111112
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
112-
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 4,
113+
IDENT
114+
| INT_NUMBER
115+
| LIFETIME_IDENT
116+
| T![self]
117+
| T![super]
118+
| T![crate]
119+
| T![Self]
120+
| T![_] => 4,
113121
// index and prefix ops
114122
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
115123
kind if kind.is_keyword() => 2,
@@ -142,19 +150,18 @@ fn hover_impl(
142150
} else {
143151
sema.descend_into_macros_with_same_text(original_token.clone())
144152
};
153+
let descended = || descended.iter();
145154

146-
// try lint hover
147-
let result = descended
148-
.iter()
155+
let result = descended()
156+
// try lint hover
149157
.find_map(|token| {
150158
// FIXME: Definition should include known lints and the like instead of having this special case here
151159
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
152160
render::try_for_lint(&attr, token)
153161
})
154-
// try item definitions
162+
// try definitions
155163
.or_else(|| {
156-
descended
157-
.iter()
164+
descended()
158165
.filter_map(|token| {
159166
let node = token.parent()?;
160167
let class = IdentClass::classify_token(sema, token)?;
@@ -175,10 +182,12 @@ fn hover_impl(
175182
})
176183
})
177184
// try keywords
178-
.or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
179-
// try rest item hover
185+
.or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
186+
// try _ hovers
187+
.or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
188+
// try rest pattern hover
180189
.or_else(|| {
181-
descended.iter().find_map(|token| {
190+
descended().find_map(|token| {
182191
if token.kind() != DOT2 {
183192
return None;
184193
}
@@ -194,51 +203,24 @@ fn hover_impl(
194203
})
195204
});
196205

197-
result
198-
.map(|mut res: HoverResult| {
199-
res.actions = dedupe_or_merge_hover_actions(res.actions);
200-
RangeInfo::new(original_token.text_range(), res)
201-
})
202-
// fallback to type hover if there aren't any other suggestions
203-
// this finds its own range instead of using the closest token's range
204-
.or_else(|| {
205-
descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token))
206-
})
207-
}
208-
209-
pub(crate) fn hover_for_definition(
210-
sema: &Semantics<'_, RootDatabase>,
211-
file_id: FileId,
212-
definition: Definition,
213-
node: &SyntaxNode,
214-
config: &HoverConfig,
215-
) -> Option<HoverResult> {
216-
let famous_defs = match &definition {
217-
Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
218-
_ => None,
219-
};
220-
render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
221-
HoverResult {
222-
markup: render::process_markup(sema.db, definition, &markup, config),
223-
actions: show_implementations_action(sema.db, definition)
224-
.into_iter()
225-
.chain(show_fn_references_action(sema.db, definition))
226-
.chain(runnable_action(sema, definition, file_id))
227-
.chain(goto_type_action_for_def(sema.db, definition))
228-
.collect(),
229-
}
206+
result.map(|mut res: HoverResult| {
207+
res.actions = dedupe_or_merge_hover_actions(res.actions);
208+
RangeInfo::new(original_token.text_range(), res)
230209
})
231210
}
232211

233212
fn hover_ranged(
234-
file: &SyntaxNode,
235-
range: syntax::TextRange,
236213
sema: &Semantics<'_, RootDatabase>,
214+
FileRange { range, .. }: FileRange,
215+
file: SyntaxNode,
237216
config: &HoverConfig,
238217
) -> Option<RangeInfo<HoverResult>> {
239218
// FIXME: make this work in attributes
240-
let expr_or_pat =
241-
file.covering_element(range).ancestors().find_map(Either::<ast::Expr, ast::Pat>::cast)?;
219+
let expr_or_pat = file
220+
.covering_element(range)
221+
.ancestors()
222+
.take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind()))
223+
.find_map(Either::<ast::Expr, ast::Pat>::cast)?;
242224
let res = match &expr_or_pat {
243225
Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
244226
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
@@ -248,7 +230,7 @@ fn hover_ranged(
248230
}
249231
_ => None,
250232
};
251-
let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
233+
let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
252234
res.map(|it| {
253235
let range = match expr_or_pat {
254236
Either::Left(it) => it.syntax().text_range(),
@@ -258,37 +240,31 @@ fn hover_ranged(
258240
})
259241
}
260242

261-
fn hover_type_fallback(
243+
pub(crate) fn hover_for_definition(
262244
sema: &Semantics<'_, RootDatabase>,
245+
file_id: FileId,
246+
definition: Definition,
247+
node: &SyntaxNode,
263248
config: &HoverConfig,
264-
token: &SyntaxToken,
265-
original_token: &SyntaxToken,
266-
) -> Option<RangeInfo<HoverResult>> {
267-
let node =
268-
token.parent_ancestors().take_while(|it| !ast::Item::can_cast(it.kind())).find(|n| {
269-
ast::Expr::can_cast(n.kind())
270-
|| ast::Pat::can_cast(n.kind())
271-
|| ast::Type::can_cast(n.kind())
272-
})?;
273-
274-
let expr_or_pat = match_ast! {
275-
match node {
276-
ast::Expr(it) => Either::Left(it),
277-
ast::Pat(it) => Either::Right(it),
278-
// If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
279-
// (e.g expanding a builtin macro). So we give up here.
280-
ast::MacroCall(_it) => return None,
281-
_ => return None,
282-
}
249+
) -> Option<HoverResult> {
250+
let famous_defs = match &definition {
251+
Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
252+
_ => None,
283253
};
284-
285-
let res = render::type_info(sema, config, &expr_or_pat)?;
286-
287-
let range = sema
288-
.original_range_opt(&node)
289-
.map(|frange| frange.range)
290-
.unwrap_or_else(|| original_token.text_range());
291-
Some(RangeInfo::new(range, res))
254+
render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
255+
HoverResult {
256+
markup: render::process_markup(sema.db, definition, &markup, config),
257+
actions: [
258+
show_implementations_action(sema.db, definition),
259+
show_fn_references_action(sema.db, definition),
260+
runnable_action(sema, definition, file_id),
261+
goto_type_action_for_def(sema.db, definition),
262+
]
263+
.into_iter()
264+
.flatten()
265+
.collect(),
266+
}
267+
})
292268
}
293269

294270
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {

crates/ide/src/hover/render.rs

Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use crate::{
2929
HoverAction, HoverConfig, HoverResult, Markup,
3030
};
3131

32-
pub(super) fn type_info(
32+
pub(super) fn type_info_of(
3333
sema: &Semantics<'_, RootDatabase>,
3434
_config: &HoverConfig,
3535
expr_or_pat: &Either<ast::Expr, ast::Pat>,
@@ -38,34 +38,7 @@ pub(super) fn type_info(
3838
Either::Left(expr) => sema.type_of_expr(expr)?,
3939
Either::Right(pat) => sema.type_of_pat(pat)?,
4040
};
41-
42-
let mut res = HoverResult::default();
43-
let mut targets: Vec<hir::ModuleDef> = Vec::new();
44-
let mut push_new_def = |item: hir::ModuleDef| {
45-
if !targets.contains(&item) {
46-
targets.push(item);
47-
}
48-
};
49-
walk_and_push_ty(sema.db, &original, &mut push_new_def);
50-
51-
res.markup = if let Some(adjusted_ty) = adjusted {
52-
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
53-
let original = original.display(sema.db).to_string();
54-
let adjusted = adjusted_ty.display(sema.db).to_string();
55-
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
56-
format!(
57-
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
58-
original,
59-
adjusted,
60-
apad = static_text_diff_len + adjusted.len().max(original.len()),
61-
opad = original.len(),
62-
)
63-
.into()
64-
} else {
65-
Markup::fenced_block(&original.display(sema.db))
66-
};
67-
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
68-
Some(res)
41+
type_info(sema, _config, original, adjusted)
6942
}
7043

7144
pub(super) fn try_expr(
@@ -217,6 +190,48 @@ pub(super) fn deref_expr(
217190
Some(res)
218191
}
219192

193+
pub(super) fn underscore(
194+
sema: &Semantics<'_, RootDatabase>,
195+
config: &HoverConfig,
196+
token: &SyntaxToken,
197+
) -> Option<HoverResult> {
198+
if token.kind() != T![_] {
199+
return None;
200+
}
201+
let parent = token.parent()?;
202+
let _it = match_ast! {
203+
match parent {
204+
ast::InferType(it) => it,
205+
ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
206+
ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
207+
_ => return None,
208+
}
209+
};
210+
// let it = infer_type.syntax().parent()?;
211+
// match_ast! {
212+
// match it {
213+
// ast::LetStmt(_it) => (),
214+
// ast::Param(_it) => (),
215+
// ast::RetType(_it) => (),
216+
// ast::TypeArg(_it) => (),
217+
218+
// ast::CastExpr(_it) => (),
219+
// ast::ParenType(_it) => (),
220+
// ast::TupleType(_it) => (),
221+
// ast::PtrType(_it) => (),
222+
// ast::RefType(_it) => (),
223+
// ast::ArrayType(_it) => (),
224+
// ast::SliceType(_it) => (),
225+
// ast::ForType(_it) => (),
226+
// _ => return None,
227+
// }
228+
// }
229+
230+
// FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
231+
// type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
232+
None
233+
}
234+
220235
pub(super) fn keyword(
221236
sema: &Semantics<'_, RootDatabase>,
222237
config: &HoverConfig,
@@ -458,6 +473,41 @@ pub(super) fn definition(
458473
markup(docs, label, mod_path)
459474
}
460475

476+
fn type_info(
477+
sema: &Semantics<'_, RootDatabase>,
478+
_config: &HoverConfig,
479+
original: hir::Type,
480+
adjusted: Option<hir::Type>,
481+
) -> Option<HoverResult> {
482+
let mut res = HoverResult::default();
483+
let mut targets: Vec<hir::ModuleDef> = Vec::new();
484+
let mut push_new_def = |item: hir::ModuleDef| {
485+
if !targets.contains(&item) {
486+
targets.push(item);
487+
}
488+
};
489+
walk_and_push_ty(sema.db, &original, &mut push_new_def);
490+
491+
res.markup = if let Some(adjusted_ty) = adjusted {
492+
walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
493+
let original = original.display(sema.db).to_string();
494+
let adjusted = adjusted_ty.display(sema.db).to_string();
495+
let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
496+
format!(
497+
"```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
498+
original,
499+
adjusted,
500+
apad = static_text_diff_len + adjusted.len().max(original.len()),
501+
opad = original.len(),
502+
)
503+
.into()
504+
} else {
505+
Markup::fenced_block(&original.display(sema.db))
506+
};
507+
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
508+
Some(res)
509+
}
510+
461511
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
462512
let name = attr.name(db);
463513
let desc = format!("#[{name}]");

0 commit comments

Comments
 (0)