]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | mod render; |
2 | ||
3 | #[cfg(test)] | |
4 | mod tests; | |
5 | ||
c0240ec0 | 6 | use std::{iter, ops::Not}; |
064997fb FG |
7 | |
8 | use either::Either; | |
c0240ec0 | 9 | use hir::{db::DefDatabase, DescendPreference, HasCrate, HasSource, LangItem, Semantics}; |
064997fb FG |
10 | use 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 | 17 | use itertools::{multizip, Itertools}; |
31ef2f64 | 18 | use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T}; |
064997fb FG |
19 | |
20 | use 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)] | |
29 | pub 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)] | |
41 | pub 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)] | |
49 | pub enum MemoryLayoutHoverRenderKind { | |
50 | Decimal, | |
51 | Hexadecimal, | |
52 | Both, | |
064997fb FG |
53 | } |
54 | ||
55 | #[derive(Clone, Debug, PartialEq, Eq)] | |
56 | pub enum HoverDocFormat { | |
57 | Markdown, | |
58 | PlainText, | |
59 | } | |
60 | ||
61 | #[derive(Debug, Clone)] | |
62 | pub enum HoverAction { | |
63 | Runnable(Runnable), | |
64 | Implementation(FilePosition), | |
65 | Reference(FilePosition), | |
66 | GoToType(Vec<HoverGotoTypeData>), | |
67 | } | |
68 | ||
69 | impl 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)] | |
89 | pub 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)] | |
96 | pub 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[] | |
107 | pub(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 |
127 | fn 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(¯o_call) { | |
214 | return Some(vec![( | |
215 | Definition::Macro(macro_), | |
216 | sema.resolve_macro_call_arm(¯o_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 | ||
311 | fn 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 | 343 | pub(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(), ¬able_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, ¬able_traits), | |
382 | ] | |
383 | .into_iter() | |
384 | .flatten() | |
385 | .collect(), | |
386 | } | |
064997fb FG |
387 | } |
388 | ||
c0240ec0 FG |
389 | fn 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 |
415 | fn 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 | ||
434 | fn 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 | ||
448 | fn 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 |
469 | fn 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 | ||
512 | fn 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 | ||
530 | fn 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 | } |