]> git.proxmox.com Git - rustc.git/blame - src/tools/rust-analyzer/crates/ide/src/hover.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / rust-analyzer / crates / ide / src / hover.rs
CommitLineData
064997fb
FG
1mod render;
2
3#[cfg(test)]
4mod tests;
5
c0240ec0 6use std::{iter, ops::Not};
064997fb
FG
7
8use either::Either;
c0240ec0 9use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics};
064997fb
FG
10use ide_db::{
11 base_db::FileRange,
add651ee 12 defs::{Definition, IdentClass, NameRefClass, OperatorClass},
064997fb
FG
13 famous_defs::FamousDefs,
14 helpers::pick_best_token,
15 FxIndexSet, RootDatabase,
16};
e8be2606 17use itertools::{multizip, Itertools};
31ef2f64 18use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
064997fb
FG
19
20use crate::{
21 doc_links::token_as_doc_comment,
9ffffee4 22 markdown_remove::remove_markdown,
064997fb 23 markup::Markup,
4b012472 24 navigation_target::UpmappingResult,
064997fb
FG
25 runnables::{runnable_fn, runnable_mod},
26 FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
27};
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct HoverConfig {
30 pub links_in_hover: bool,
fe692bf9 31 pub memory_layout: Option<MemoryLayoutHoverConfig>,
9ffffee4 32 pub documentation: bool,
f2b60f7d 33 pub keywords: bool,
9ffffee4 34 pub format: HoverDocFormat,
c620b35d 35 pub max_trait_assoc_items_count: Option<usize>,
31ef2f64
FG
36 pub max_fields_count: Option<usize>,
37 pub max_enum_variants_count: Option<usize>,
fe692bf9
FG
38}
39
40#[derive(Copy, Clone, Debug, PartialEq, Eq)]
41pub struct MemoryLayoutHoverConfig {
42 pub size: Option<MemoryLayoutHoverRenderKind>,
43 pub offset: Option<MemoryLayoutHoverRenderKind>,
44 pub alignment: Option<MemoryLayoutHoverRenderKind>,
45 pub niches: bool,
46}
47
48#[derive(Copy, Clone, Debug, PartialEq, Eq)]
49pub enum MemoryLayoutHoverRenderKind {
50 Decimal,
51 Hexadecimal,
52 Both,
064997fb
FG
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
56pub enum HoverDocFormat {
57 Markdown,
58 PlainText,
59}
60
61#[derive(Debug, Clone)]
62pub enum HoverAction {
63 Runnable(Runnable),
64 Implementation(FilePosition),
65 Reference(FilePosition),
66 GoToType(Vec<HoverGotoTypeData>),
67}
68
69impl HoverAction {
c0240ec0 70 fn goto_type_from_targets(db: &RootDatabase, targets: Vec<hir::ModuleDef>) -> Option<Self> {
064997fb
FG
71 let targets = targets
72 .into_iter()
73 .filter_map(|it| {
74 Some(HoverGotoTypeData {
75 mod_path: render::path(
76 db,
77 it.module(db)?,
fe692bf9 78 it.name(db).map(|name| name.display(db).to_string()),
064997fb 79 ),
4b012472 80 nav: it.try_to_nav(db)?.call_site(),
064997fb
FG
81 })
82 })
c0240ec0
FG
83 .collect::<Vec<_>>();
84 targets.is_empty().not().then_some(HoverAction::GoToType(targets))
064997fb
FG
85 }
86}
87
88#[derive(Debug, Clone, Eq, PartialEq, Hash)]
89pub struct HoverGotoTypeData {
90 pub mod_path: String,
91 pub nav: NavigationTarget,
92}
93
94/// Contains the results when hovering over an item
95#[derive(Debug, Default)]
96pub struct HoverResult {
97 pub markup: Markup,
98 pub actions: Vec<HoverAction>,
99}
100
101// Feature: Hover
102//
103// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
104// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
105//
106// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
107pub(crate) fn hover(
108 db: &RootDatabase,
9ffffee4 109 frange @ FileRange { file_id, range }: FileRange,
064997fb
FG
110 config: &HoverConfig,
111) -> Option<RangeInfo<HoverResult>> {
112 let sema = &hir::Semantics::new(db);
113 let file = sema.parse(file_id).syntax().clone();
9ffffee4
FG
114 let mut res = if range.is_empty() {
115 hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
116 } else {
117 hover_ranged(sema, frange, file, config)
118 }?;
064997fb 119
9ffffee4
FG
120 if let HoverDocFormat::PlainText = config.format {
121 res.info.markup = remove_markdown(res.info.markup.as_str()).into();
064997fb 122 }
9ffffee4
FG
123 Some(res)
124}
064997fb 125
c620b35d 126#[allow(clippy::field_reassign_with_default)]
9ffffee4
FG
127fn hover_simple(
128 sema: &Semantics<'_, RootDatabase>,
129 FilePosition { file_id, offset }: FilePosition,
130 file: SyntaxNode,
131 config: &HoverConfig,
132) -> Option<RangeInfo<HoverResult>> {
064997fb 133 let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
9ffffee4
FG
134 IDENT
135 | INT_NUMBER
136 | LIFETIME_IDENT
137 | T![self]
138 | T![super]
139 | T![crate]
140 | T![Self]
141 | T![_] => 4,
fe692bf9
FG
142 // index and prefix ops and closure pipe
143 T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] | T![|] => 3,
f2b60f7d 144 kind if kind.is_keyword() => 2,
064997fb
FG
145 T!['('] | T![')'] => 2,
146 kind if kind.is_trivia() => 0,
147 _ => 1,
148 })?;
149
150 if let Some(doc_comment) = token_as_doc_comment(&original_token) {
151 cov_mark::hit!(no_highlight_on_comment_hover);
152 return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
e8be2606 153 let res = hover_for_definition(sema, file_id, def, &node, None, config);
064997fb
FG
154 Some(RangeInfo::new(range, res))
155 });
156 }
157
4b012472
FG
158 if let Some((range, resolution)) =
159 sema.check_for_format_args_template(original_token.clone(), offset)
160 {
161 let res = hover_for_definition(
162 sema,
163 file_id,
164 Definition::from(resolution?),
165 &original_token.parent()?,
e8be2606 166 None,
4b012472 167 config,
c620b35d 168 );
4b012472
FG
169 return Some(RangeInfo::new(range, res));
170 }
171
487cf647
FG
172 let in_attr = original_token
173 .parent_ancestors()
174 .filter_map(ast::Item::cast)
175 .any(|item| sema.is_attr_macro_call(&item))
176 && !matches!(
177 original_token.parent().and_then(ast::TokenTree::cast),
178 Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
179 );
9c376795 180
f2b60f7d
FG
181 // prefer descending the same token kind in attribute expansions, in normal macros text
182 // equivalency is more important
4b012472
FG
183 let descended = sema.descend_into_macros(
184 if in_attr { DescendPreference::SameKind } else { DescendPreference::SameText },
185 original_token.clone(),
186 );
9ffffee4 187 let descended = || descended.iter();
064997fb 188
9ffffee4
FG
189 let result = descended()
190 // try lint hover
9c376795
FG
191 .find_map(|token| {
192 // FIXME: Definition should include known lints and the like instead of having this special case here
193 let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
194 render::try_for_lint(&attr, token)
064997fb 195 })
9ffffee4 196 // try definitions
9c376795 197 .or_else(|| {
9ffffee4 198 descended()
9c376795
FG
199 .filter_map(|token| {
200 let node = token.parent()?;
e8be2606
FG
201
202 // special case macro calls, we wanna render the invoked arm index
203 if let Some(name) = ast::NameRef::cast(node.clone()) {
204 if let Some(path_seg) =
205 name.syntax().parent().and_then(ast::PathSegment::cast)
206 {
207 if let Some(macro_call) = path_seg
208 .parent_path()
209 .syntax()
210 .parent()
211 .and_then(ast::MacroCall::cast)
212 {
213 if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
214 return Some(vec![(
215 Definition::Macro(macro_),
216 sema.resolve_macro_call_arm(&macro_call),
217 node,
218 )]);
219 }
220 }
221 }
222 }
223
4b012472 224 match IdentClass::classify_node(sema, &node)? {
9c376795
FG
225 // It's better for us to fall back to the keyword hover here,
226 // rendering poll is very confusing
4b012472
FG
227 IdentClass::Operator(OperatorClass::Await(_)) => None,
228
229 IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
230 decl,
231 ..
e8be2606 232 }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
4b012472
FG
233
234 class => Some(
e8be2606 235 multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
4b012472
FG
236 .collect::<Vec<_>>(),
237 ),
add651ee 238 }
9c376795
FG
239 })
240 .flatten()
e8be2606
FG
241 .unique_by(|&(def, _, _)| def)
242 .map(|(def, macro_arm, node)| {
243 hover_for_definition(sema, file_id, def, &node, macro_arm, config)
244 })
9c376795
FG
245 .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
246 acc.actions.extend(actions);
247 acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
248 acc
249 })
250 })
251 // try keywords
9ffffee4
FG
252 .or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
253 // try _ hovers
254 .or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
255 // try rest pattern hover
9c376795 256 .or_else(|| {
9ffffee4 257 descended().find_map(|token| {
9c376795
FG
258 if token.kind() != DOT2 {
259 return None;
260 }
064997fb 261
9c376795
FG
262 let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
263 let record_pat_field_list =
264 rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
064997fb 265
9c376795
FG
266 let record_pat =
267 record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
268
269 Some(render::struct_rest_pat(sema, config, &record_pat))
270 })
9c376795 271 })
9ffffee4 272 // try () call hovers
9c376795 273 .or_else(|| {
9ffffee4
FG
274 descended().find_map(|token| {
275 if token.kind() != T!['('] && token.kind() != T![')'] {
276 return None;
277 }
278 let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
279 let call_expr = syntax::match_ast! {
280 match arg_list {
281 ast::CallExpr(expr) => expr.into(),
282 ast::MethodCallExpr(expr) => expr.into(),
283 _ => return None,
284 }
285 };
286 render::type_info_of(sema, config, &Either::Left(call_expr))
287 })
fe692bf9
FG
288 })
289 // try closure
290 .or_else(|| {
291 descended().find_map(|token| {
292 if token.kind() != T![|] {
293 return None;
294 }
295 let c = token.parent().and_then(|x| x.parent()).and_then(ast::ClosureExpr::cast)?;
296 render::closure_expr(sema, config, c)
297 })
c0240ec0
FG
298 })
299 // tokens
300 .or_else(|| {
31ef2f64
FG
301 render::literal(sema, original_token.clone())
302 .map(|markup| HoverResult { markup, actions: vec![] })
9ffffee4 303 });
064997fb 304
9ffffee4
FG
305 result.map(|mut res: HoverResult| {
306 res.actions = dedupe_or_merge_hover_actions(res.actions);
307 RangeInfo::new(original_token.text_range(), res)
064997fb
FG
308 })
309}
310
311fn hover_ranged(
064997fb 312 sema: &Semantics<'_, RootDatabase>,
9ffffee4
FG
313 FileRange { range, .. }: FileRange,
314 file: SyntaxNode,
064997fb
FG
315 config: &HoverConfig,
316) -> Option<RangeInfo<HoverResult>> {
317 // FIXME: make this work in attributes
9ffffee4
FG
318 let expr_or_pat = file
319 .covering_element(range)
320 .ancestors()
321 .take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind()))
322 .find_map(Either::<ast::Expr, ast::Pat>::cast)?;
064997fb
FG
323 let res = match &expr_or_pat {
324 Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
325 Either::Left(ast::Expr::PrefixExpr(prefix_expr))
326 if prefix_expr.op_kind() == Some(ast::UnaryOp::Deref) =>
327 {
328 render::deref_expr(sema, config, prefix_expr)
329 }
330 _ => None,
331 };
9ffffee4 332 let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
064997fb
FG
333 res.map(|it| {
334 let range = match expr_or_pat {
335 Either::Left(it) => it.syntax().text_range(),
336 Either::Right(it) => it.syntax().text_range(),
337 };
338 RangeInfo::new(range, it)
339 })
340}
341
c0240ec0 342// FIXME: Why is this pub(crate)?
9ffffee4 343pub(crate) fn hover_for_definition(
064997fb 344 sema: &Semantics<'_, RootDatabase>,
9ffffee4 345 file_id: FileId,
c0240ec0 346 def: Definition,
4b012472 347 scope_node: &SyntaxNode,
e8be2606 348 macro_arm: Option<u32>,
064997fb 349 config: &HoverConfig,
c620b35d 350) -> HoverResult {
c0240ec0 351 let famous_defs = match &def {
c620b35d 352 Definition::BuiltinType(_) => sema.scope(scope_node).map(|it| FamousDefs(sema, it.krate())),
9ffffee4 353 _ => None,
064997fb 354 };
c0240ec0
FG
355
356 let db = sema.db;
357 let def_ty = match def {
358 Definition::Local(it) => Some(it.ty(db)),
359 Definition::GenericParam(hir::GenericParam::ConstParam(it)) => Some(it.ty(db)),
360 Definition::GenericParam(hir::GenericParam::TypeParam(it)) => Some(it.ty(db)),
361 Definition::Field(field) => Some(field.ty(db)),
362 Definition::TupleField(it) => Some(it.ty(db)),
363 Definition::Function(it) => Some(it.ty(db)),
364 Definition::Adt(it) => Some(it.ty(db)),
365 Definition::Const(it) => Some(it.ty(db)),
366 Definition::Static(it) => Some(it.ty(db)),
367 Definition::TypeAlias(it) => Some(it.ty(db)),
368 Definition::BuiltinType(it) => Some(it.ty(db)),
369 _ => None,
370 };
371 let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
372
e8be2606
FG
373 let markup =
374 render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, macro_arm, config);
c620b35d
FG
375 HoverResult {
376 markup: render::process_markup(sema.db, def, &markup, config),
377 actions: [
378 show_implementations_action(sema.db, def),
379 show_fn_references_action(sema.db, def),
380 runnable_action(sema, def, file_id),
381 goto_type_action_for_def(sema.db, def, &notable_traits),
382 ]
383 .into_iter()
384 .flatten()
385 .collect(),
386 }
064997fb
FG
387}
388
c0240ec0
FG
389fn notable_traits(
390 db: &RootDatabase,
391 ty: &hir::Type,
392) -> Vec<(hir::Trait, Vec<(Option<hir::Type>, hir::Name)>)> {
393 db.notable_traits_in_deps(ty.krate(db).into())
394 .iter()
395 .flat_map(|it| &**it)
396 .filter_map(move |&trait_| {
397 let trait_ = trait_.into();
398 ty.impls_trait(db, trait_, &[]).then(|| {
399 (
400 trait_,
401 trait_
402 .items(db)
403 .into_iter()
404 .filter_map(hir::AssocItem::as_type_alias)
405 .map(|alias| {
406 (ty.normalize_trait_assoc_type(db, &[], alias), alias.name(db))
407 })
408 .collect::<Vec<_>>(),
409 )
410 })
411 })
412 .collect::<Vec<_>>()
413}
414
064997fb
FG
415fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
416 fn to_action(nav_target: NavigationTarget) -> HoverAction {
417 HoverAction::Implementation(FilePosition {
418 file_id: nav_target.file_id,
419 offset: nav_target.focus_or_full_range().start(),
420 })
421 }
422
423 let adt = match def {
4b012472
FG
424 Definition::Trait(it) => {
425 return it.try_to_nav(db).map(UpmappingResult::call_site).map(to_action)
426 }
064997fb
FG
427 Definition::Adt(it) => Some(it),
428 Definition::SelfType(it) => it.self_ty(db).as_adt(),
429 _ => None,
430 }?;
4b012472 431 adt.try_to_nav(db).map(UpmappingResult::call_site).map(to_action)
064997fb
FG
432}
433
434fn show_fn_references_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
435 match def {
4b012472
FG
436 Definition::Function(it) => {
437 it.try_to_nav(db).map(UpmappingResult::call_site).map(|nav_target| {
438 HoverAction::Reference(FilePosition {
439 file_id: nav_target.file_id,
440 offset: nav_target.focus_or_full_range().start(),
441 })
064997fb 442 })
4b012472 443 }
064997fb
FG
444 _ => None,
445 }
446}
447
448fn runnable_action(
449 sema: &hir::Semantics<'_, RootDatabase>,
450 def: Definition,
451 file_id: FileId,
452) -> Option<HoverAction> {
453 match def {
454 Definition::Module(it) => runnable_mod(sema, it).map(HoverAction::Runnable),
455 Definition::Function(func) => {
456 let src = func.source(sema.db)?;
457 if src.file_id != file_id.into() {
458 cov_mark::hit!(hover_macro_generated_struct_fn_doc_comment);
459 cov_mark::hit!(hover_macro_generated_struct_fn_doc_attr);
460 return None;
461 }
462
463 runnable_fn(sema, func).map(HoverAction::Runnable)
464 }
465 _ => None,
466 }
467}
468
c0240ec0
FG
469fn goto_type_action_for_def(
470 db: &RootDatabase,
471 def: Definition,
472 notable_traits: &[(hir::Trait, Vec<(Option<hir::Type>, hir::Name)>)],
473) -> Option<HoverAction> {
064997fb
FG
474 let mut targets: Vec<hir::ModuleDef> = Vec::new();
475 let mut push_new_def = |item: hir::ModuleDef| {
476 if !targets.contains(&item) {
477 targets.push(item);
478 }
479 };
480
c0240ec0
FG
481 for &(trait_, ref assocs) in notable_traits {
482 push_new_def(trait_.into());
483 assocs.iter().filter_map(|(ty, _)| ty.as_ref()).for_each(|ty| {
484 walk_and_push_ty(db, ty, &mut push_new_def);
485 });
486 }
487
064997fb 488 if let Definition::GenericParam(hir::GenericParam::TypeParam(it)) = def {
fe692bf9
FG
489 let krate = it.module(db).krate();
490 let sized_trait =
491 db.lang_item(krate.into(), LangItem::Sized).and_then(|lang_item| lang_item.as_trait());
492
493 it.trait_bounds(db)
494 .into_iter()
495 .filter(|&it| Some(it.into()) != sized_trait)
496 .for_each(|it| push_new_def(it.into()));
064997fb
FG
497 } else {
498 let ty = match def {
499 Definition::Local(it) => it.ty(db),
500 Definition::GenericParam(hir::GenericParam::ConstParam(it)) => it.ty(db),
501 Definition::Field(field) => field.ty(db),
502 Definition::Function(function) => function.ret_type(db),
c0240ec0 503 _ => return HoverAction::goto_type_from_targets(db, targets),
064997fb
FG
504 };
505
506 walk_and_push_ty(db, &ty, &mut push_new_def);
507 }
508
c0240ec0 509 HoverAction::goto_type_from_targets(db, targets)
064997fb
FG
510}
511
512fn walk_and_push_ty(
513 db: &RootDatabase,
514 ty: &hir::Type,
515 push_new_def: &mut dyn FnMut(hir::ModuleDef),
516) {
517 ty.walk(db, |t| {
518 if let Some(adt) = t.as_adt() {
519 push_new_def(adt.into());
520 } else if let Some(trait_) = t.as_dyn_trait() {
521 push_new_def(trait_.into());
522 } else if let Some(traits) = t.as_impl_traits(db) {
523 traits.for_each(|it| push_new_def(it.into()));
524 } else if let Some(trait_) = t.as_associated_type_parent_trait(db) {
525 push_new_def(trait_.into());
526 }
527 });
528}
529
530fn dedupe_or_merge_hover_actions(actions: Vec<HoverAction>) -> Vec<HoverAction> {
531 let mut deduped_actions = Vec::with_capacity(actions.len());
532 let mut go_to_type_targets = FxIndexSet::default();
533
534 let mut seen_implementation = false;
535 let mut seen_reference = false;
536 let mut seen_runnable = false;
537 for action in actions {
538 match action {
539 HoverAction::GoToType(targets) => {
540 go_to_type_targets.extend(targets);
541 }
542 HoverAction::Implementation(..) => {
543 if !seen_implementation {
544 seen_implementation = true;
545 deduped_actions.push(action);
546 }
547 }
548 HoverAction::Reference(..) => {
549 if !seen_reference {
550 seen_reference = true;
551 deduped_actions.push(action);
552 }
553 }
554 HoverAction::Runnable(..) => {
555 if !seen_runnable {
556 seen_runnable = true;
557 deduped_actions.push(action);
558 }
559 }
560 };
561 }
562
563 if !go_to_type_targets.is_empty() {
c0240ec0
FG
564 deduped_actions.push(HoverAction::GoToType(
565 go_to_type_targets.into_iter().sorted_by(|a, b| a.mod_path.cmp(&b.mod_path)).collect(),
566 ));
064997fb
FG
567 }
568
569 deduped_actions
570}