• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Logic for rendering the different hover messages
2 use std::fmt::Display;
3 
4 use either::Either;
5 use hir::{
6     Adt, AsAssocItem, AttributeTemplate, CaptureKind, HasAttrs, HasSource, HirDisplay, Layout,
7     LayoutError, Semantics, TypeInfo,
8 };
9 use ide_db::{
10     base_db::SourceDatabase,
11     defs::Definition,
12     famous_defs::FamousDefs,
13     generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
14     syntax_helpers::insert_whitespace_into_node,
15     RootDatabase,
16 };
17 use itertools::Itertools;
18 use stdx::format_to;
19 use syntax::{
20     algo,
21     ast::{self, RecordPat},
22     match_ast, AstNode, Direction,
23     SyntaxKind::{LET_EXPR, LET_STMT},
24     SyntaxToken, T,
25 };
26 
27 use crate::{
28     doc_links::{remove_links, rewrite_links},
29     hover::walk_and_push_ty,
30     HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig,
31     MemoryLayoutHoverRenderKind,
32 };
33 
type_info_of( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, expr_or_pat: &Either<ast::Expr, ast::Pat>, ) -> Option<HoverResult>34 pub(super) fn type_info_of(
35     sema: &Semantics<'_, RootDatabase>,
36     _config: &HoverConfig,
37     expr_or_pat: &Either<ast::Expr, ast::Pat>,
38 ) -> Option<HoverResult> {
39     let ty_info = match expr_or_pat {
40         Either::Left(expr) => sema.type_of_expr(expr)?,
41         Either::Right(pat) => sema.type_of_pat(pat)?,
42     };
43     type_info(sema, _config, ty_info)
44 }
45 
closure_expr( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, c: ast::ClosureExpr, ) -> Option<HoverResult>46 pub(super) fn closure_expr(
47     sema: &Semantics<'_, RootDatabase>,
48     config: &HoverConfig,
49     c: ast::ClosureExpr,
50 ) -> Option<HoverResult> {
51     let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?;
52     closure_ty(sema, config, &TypeInfo { original, adjusted: None })
53 }
54 
try_expr( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, try_expr: &ast::TryExpr, ) -> Option<HoverResult>55 pub(super) fn try_expr(
56     sema: &Semantics<'_, RootDatabase>,
57     _config: &HoverConfig,
58     try_expr: &ast::TryExpr,
59 ) -> Option<HoverResult> {
60     let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
61     let mut ancestors = try_expr.syntax().ancestors();
62     let mut body_ty = loop {
63         let next = ancestors.next()?;
64         break match_ast! {
65             match next {
66                 ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
67                 ast::Item(__) => return None,
68                 ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
69                 ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
70                     sema.type_of_expr(&block_expr.into())?.original
71                 } else {
72                     continue;
73                 },
74                 _ => continue,
75             }
76         };
77     };
78 
79     if inner_ty == body_ty {
80         return None;
81     }
82 
83     let mut inner_ty = inner_ty;
84     let mut s = "Try Target".to_owned();
85 
86     let adts = inner_ty.as_adt().zip(body_ty.as_adt());
87     if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
88         let famous_defs = FamousDefs(sema, sema.scope(try_expr.syntax())?.krate());
89         // special case for two options, there is no value in showing them
90         if let Some(option_enum) = famous_defs.core_option_Option() {
91             if inner == option_enum && body == option_enum {
92                 cov_mark::hit!(hover_try_expr_opt_opt);
93                 return None;
94             }
95         }
96 
97         // special case two results to show the error variants only
98         if let Some(result_enum) = famous_defs.core_result_Result() {
99             if inner == result_enum && body == result_enum {
100                 let error_type_args =
101                     inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
102                 if let Some((inner, body)) = error_type_args {
103                     inner_ty = inner;
104                     body_ty = body;
105                     s = "Try Error".to_owned();
106                 }
107             }
108         }
109     }
110 
111     let mut res = HoverResult::default();
112 
113     let mut targets: Vec<hir::ModuleDef> = Vec::new();
114     let mut push_new_def = |item: hir::ModuleDef| {
115         if !targets.contains(&item) {
116             targets.push(item);
117         }
118     };
119     walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
120     walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
121     res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
122 
123     let inner_ty = inner_ty.display(sema.db).to_string();
124     let body_ty = body_ty.display(sema.db).to_string();
125     let ty_len_max = inner_ty.len().max(body_ty.len());
126 
127     let l = "Propagated as: ".len() - " Type: ".len();
128     let static_text_len_diff = l as isize - s.len() as isize;
129     let tpad = static_text_len_diff.max(0) as usize;
130     let ppad = static_text_len_diff.min(0).abs() as usize;
131 
132     res.markup = format!(
133         "```text\n{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n```\n",
134         s,
135         inner_ty,
136         body_ty,
137         pad0 = ty_len_max + tpad,
138         pad1 = ty_len_max + ppad,
139     )
140     .into();
141     Some(res)
142 }
143 
deref_expr( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, deref_expr: &ast::PrefixExpr, ) -> Option<HoverResult>144 pub(super) fn deref_expr(
145     sema: &Semantics<'_, RootDatabase>,
146     _config: &HoverConfig,
147     deref_expr: &ast::PrefixExpr,
148 ) -> Option<HoverResult> {
149     let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
150     let TypeInfo { original, adjusted } =
151         sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
152 
153     let mut res = HoverResult::default();
154     let mut targets: Vec<hir::ModuleDef> = Vec::new();
155     let mut push_new_def = |item: hir::ModuleDef| {
156         if !targets.contains(&item) {
157             targets.push(item);
158         }
159     };
160     walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
161     walk_and_push_ty(sema.db, &original, &mut push_new_def);
162 
163     res.markup = if let Some(adjusted_ty) = adjusted {
164         walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
165         let original = original.display(sema.db).to_string();
166         let adjusted = adjusted_ty.display(sema.db).to_string();
167         let inner = inner_ty.display(sema.db).to_string();
168         let type_len = "To type: ".len();
169         let coerced_len = "Coerced to: ".len();
170         let deref_len = "Dereferenced from: ".len();
171         let max_len = (original.len() + type_len)
172             .max(adjusted.len() + coerced_len)
173             .max(inner.len() + deref_len);
174         format!(
175             "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
176             inner,
177             original,
178             adjusted,
179             ipad = max_len - deref_len,
180             apad = max_len - type_len,
181             opad = max_len - coerced_len,
182         )
183         .into()
184     } else {
185         let original = original.display(sema.db).to_string();
186         let inner = inner_ty.display(sema.db).to_string();
187         let type_len = "To type: ".len();
188         let deref_len = "Dereferenced from: ".len();
189         let max_len = (original.len() + type_len).max(inner.len() + deref_len);
190         format!(
191             "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\n```\n",
192             inner,
193             original,
194             ipad = max_len - deref_len,
195             apad = max_len - type_len,
196         )
197         .into()
198     };
199     res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
200 
201     Some(res)
202 }
203 
underscore( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, token: &SyntaxToken, ) -> Option<HoverResult>204 pub(super) fn underscore(
205     sema: &Semantics<'_, RootDatabase>,
206     config: &HoverConfig,
207     token: &SyntaxToken,
208 ) -> Option<HoverResult> {
209     if token.kind() != T![_] {
210         return None;
211     }
212     let parent = token.parent()?;
213     let _it = match_ast! {
214         match parent {
215             ast::InferType(it) => it,
216             ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
217             ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
218             _ => return None,
219         }
220     };
221     // let it = infer_type.syntax().parent()?;
222     // match_ast! {
223     //     match it {
224     //         ast::LetStmt(_it) => (),
225     //         ast::Param(_it) => (),
226     //         ast::RetType(_it) => (),
227     //         ast::TypeArg(_it) => (),
228 
229     //         ast::CastExpr(_it) => (),
230     //         ast::ParenType(_it) => (),
231     //         ast::TupleType(_it) => (),
232     //         ast::PtrType(_it) => (),
233     //         ast::RefType(_it) => (),
234     //         ast::ArrayType(_it) => (),
235     //         ast::SliceType(_it) => (),
236     //         ast::ForType(_it) => (),
237     //         _ => return None,
238     //     }
239     // }
240 
241     // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
242     // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
243     None
244 }
245 
keyword( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, token: &SyntaxToken, ) -> Option<HoverResult>246 pub(super) fn keyword(
247     sema: &Semantics<'_, RootDatabase>,
248     config: &HoverConfig,
249     token: &SyntaxToken,
250 ) -> Option<HoverResult> {
251     if !token.kind().is_keyword() || !config.documentation || !config.keywords {
252         return None;
253     }
254     let parent = token.parent()?;
255     let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate());
256 
257     let KeywordHint { description, keyword_mod, actions } = keyword_hints(sema, token, parent);
258 
259     let doc_owner = find_std_module(&famous_defs, &keyword_mod)?;
260     let docs = doc_owner.attrs(sema.db).docs()?;
261     let markup = process_markup(
262         sema.db,
263         Definition::Module(doc_owner),
264         &markup(Some(docs.into()), description, None)?,
265         config,
266     );
267     Some(HoverResult { markup, actions })
268 }
269 
270 /// Returns missing types in a record pattern.
271 /// Only makes sense when there's a rest pattern in the record pattern.
272 /// i.e. `let S {a, ..} = S {a: 1, b: 2}`
struct_rest_pat( sema: &Semantics<'_, RootDatabase>, _config: &HoverConfig, pattern: &RecordPat, ) -> HoverResult273 pub(super) fn struct_rest_pat(
274     sema: &Semantics<'_, RootDatabase>,
275     _config: &HoverConfig,
276     pattern: &RecordPat,
277 ) -> HoverResult {
278     let missing_fields = sema.record_pattern_missing_fields(pattern);
279 
280     // if there are no missing fields, the end result is a hover that shows ".."
281     // should be left in to indicate that there are no more fields in the pattern
282     // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
283 
284     let mut res = HoverResult::default();
285     let mut targets: Vec<hir::ModuleDef> = Vec::new();
286     let mut push_new_def = |item: hir::ModuleDef| {
287         if !targets.contains(&item) {
288             targets.push(item);
289         }
290     };
291     for (_, t) in &missing_fields {
292         walk_and_push_ty(sema.db, t, &mut push_new_def);
293     }
294 
295     res.markup = {
296         let mut s = String::from(".., ");
297         for (f, _) in &missing_fields {
298             s += f.display(sema.db).to_string().as_ref();
299             s += ", ";
300         }
301         // get rid of trailing comma
302         s.truncate(s.len() - 2);
303 
304         Markup::fenced_block(&s)
305     };
306     res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
307     res
308 }
309 
try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult>310 pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
311     let (path, tt) = attr.as_simple_call()?;
312     if !tt.syntax().text_range().contains(token.text_range().start()) {
313         return None;
314     }
315     let (is_clippy, lints) = match &*path {
316         "feature" => (false, FEATURES),
317         "allow" | "deny" | "forbid" | "warn" => {
318             let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
319                 .filter(|t| t.kind() == T![:])
320                 .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
321                 .filter(|t| t.kind() == T![:])
322                 .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
323                 .map_or(false, |t| {
324                     t.kind() == T![ident] && t.into_token().map_or(false, |t| t.text() == "clippy")
325                 });
326             if is_clippy {
327                 (true, CLIPPY_LINTS)
328             } else {
329                 (false, DEFAULT_LINTS)
330             }
331         }
332         _ => return None,
333     };
334 
335     let tmp;
336     let needle = if is_clippy {
337         tmp = format!("clippy::{}", token.text());
338         &tmp
339     } else {
340         &*token.text()
341     };
342 
343     let lint =
344         lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
345     Some(HoverResult {
346         markup: Markup::from(format!("```\n{}\n```\n___\n\n{}", lint.label, lint.description)),
347         ..Default::default()
348     })
349 }
350 
process_markup( db: &RootDatabase, def: Definition, markup: &Markup, config: &HoverConfig, ) -> Markup351 pub(super) fn process_markup(
352     db: &RootDatabase,
353     def: Definition,
354     markup: &Markup,
355     config: &HoverConfig,
356 ) -> Markup {
357     let markup = markup.as_str();
358     let markup =
359         if config.links_in_hover { rewrite_links(db, markup, def) } else { remove_links(markup) };
360     Markup::from(markup)
361 }
362 
definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>363 fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> {
364     match def {
365         Definition::Field(f) => Some(f.parent_def(db).name(db)),
366         Definition::Local(l) => l.parent(db).name(db),
367         Definition::Function(f) => match f.as_assoc_item(db)?.container(db) {
368             hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
369             hir::AssocItemContainer::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
370         },
371         Definition::Variant(e) => Some(e.parent_enum(db).name(db)),
372         _ => None,
373     }
374     .map(|name| name.display(db).to_string())
375 }
376 
path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String377 pub(super) fn path(db: &RootDatabase, module: hir::Module, item_name: Option<String>) -> String {
378     let crate_name =
379         db.crate_graph()[module.krate().into()].display_name.as_ref().map(|it| it.to_string());
380     let module_path = module
381         .path_to_root(db)
382         .into_iter()
383         .rev()
384         .flat_map(|it| it.name(db).map(|name| name.display(db).to_string()));
385     crate_name.into_iter().chain(module_path).chain(item_name).join("::")
386 }
387 
definition( db: &RootDatabase, def: Definition, famous_defs: Option<&FamousDefs<'_, '_>>, config: &HoverConfig, ) -> Option<Markup>388 pub(super) fn definition(
389     db: &RootDatabase,
390     def: Definition,
391     famous_defs: Option<&FamousDefs<'_, '_>>,
392     config: &HoverConfig,
393 ) -> Option<Markup> {
394     let mod_path = definition_mod_path(db, &def);
395     let (label, docs) = match def {
396         Definition::Macro(it) => label_and_docs(db, it),
397         Definition::Field(it) => label_and_layout_info_and_docs(
398             db,
399             it,
400             config,
401             |&it| it.layout(db),
402             |_| {
403                 let var_def = it.parent_def(db);
404                 let id = it.index();
405                 match var_def {
406                     hir::VariantDef::Struct(s) => {
407                         Adt::from(s).layout(db).ok().and_then(|layout| layout.field_offset(id))
408                     }
409                     _ => None,
410                 }
411             },
412         ),
413         Definition::Module(it) => label_and_docs(db, it),
414         Definition::Function(it) => label_and_docs(db, it),
415         Definition::Adt(it) => {
416             label_and_layout_info_and_docs(db, it, config, |&it| it.layout(db), |_| None)
417         }
418         Definition::Variant(it) => label_value_and_layout_info_and_docs(
419             db,
420             it,
421             config,
422             |&it| {
423                 if !it.parent_enum(db).is_data_carrying(db) {
424                     match it.eval(db) {
425                         Ok(x) => {
426                             Some(if x >= 10 { format!("{x} ({x:#X})") } else { format!("{x}") })
427                         }
428                         Err(_) => it.value(db).map(|x| format!("{x:?}")),
429                     }
430                 } else {
431                     None
432                 }
433             },
434             |it| it.layout(db),
435             |layout| layout.enum_tag_size(),
436         ),
437         Definition::Const(it) => label_value_and_docs(db, it, |it| {
438             let body = it.render_eval(db);
439             match body {
440                 Ok(x) => Some(x),
441                 Err(_) => {
442                     let source = it.source(db)?;
443                     let mut body = source.value.body()?.syntax().clone();
444                     if source.file_id.is_macro() {
445                         body = insert_whitespace_into_node::insert_ws_into(body);
446                     }
447                     Some(body.to_string())
448                 }
449             }
450         }),
451         Definition::Static(it) => label_value_and_docs(db, it, |it| {
452             let source = it.source(db)?;
453             let mut body = source.value.body()?.syntax().clone();
454             if source.file_id.is_macro() {
455                 body = insert_whitespace_into_node::insert_ws_into(body);
456             }
457             Some(body.to_string())
458         }),
459         Definition::Trait(it) => label_and_docs(db, it),
460         Definition::TraitAlias(it) => label_and_docs(db, it),
461         Definition::TypeAlias(it) => {
462             label_and_layout_info_and_docs(db, it, config, |&it| it.ty(db).layout(db), |_| None)
463         }
464         Definition::BuiltinType(it) => {
465             return famous_defs
466                 .and_then(|fd| builtin(fd, it))
467                 .or_else(|| Some(Markup::fenced_block(&it.name().display(db))))
468         }
469         Definition::Local(it) => return local(db, it, config),
470         Definition::SelfType(impl_def) => {
471             impl_def.self_ty(db).as_adt().map(|adt| label_and_docs(db, adt))?
472         }
473         Definition::GenericParam(it) => label_and_docs(db, it),
474         Definition::Label(it) => return Some(Markup::fenced_block(&it.name(db).display(db))),
475         // FIXME: We should be able to show more info about these
476         Definition::BuiltinAttr(it) => return render_builtin_attr(db, it),
477         Definition::ToolModule(it) => return Some(Markup::fenced_block(&it.name(db))),
478         Definition::DeriveHelper(it) => {
479             (format!("derive_helper {}", it.name(db).display(db)), None)
480         }
481     };
482 
483     let docs = docs
484         .filter(|_| config.documentation)
485         .or_else(|| {
486             // docs are missing, for assoc items of trait impls try to fall back to the docs of the
487             // original item of the trait
488             let assoc = def.as_assoc_item(db)?;
489             let trait_ = assoc.containing_trait_impl(db)?;
490             let name = Some(assoc.name(db)?);
491             let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
492             item.docs(db)
493         })
494         .map(Into::into);
495     markup(docs, label, mod_path)
496 }
497 
type_info( sema: &Semantics<'_, RootDatabase>, config: &HoverConfig, ty: TypeInfo, ) -> Option<HoverResult>498 fn type_info(
499     sema: &Semantics<'_, RootDatabase>,
500     config: &HoverConfig,
501     ty: TypeInfo,
502 ) -> Option<HoverResult> {
503     if let Some(res) = closure_ty(sema, config, &ty) {
504         return Some(res);
505     };
506     let TypeInfo { original, adjusted } = ty;
507     let mut res = HoverResult::default();
508     let mut targets: Vec<hir::ModuleDef> = Vec::new();
509     let mut push_new_def = |item: hir::ModuleDef| {
510         if !targets.contains(&item) {
511             targets.push(item);
512         }
513     };
514     walk_and_push_ty(sema.db, &original, &mut push_new_def);
515 
516     res.markup = if let Some(adjusted_ty) = adjusted {
517         walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
518         let original = original.display(sema.db).to_string();
519         let adjusted = adjusted_ty.display(sema.db).to_string();
520         let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
521         format!(
522             "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
523             original,
524             adjusted,
525             apad = static_text_diff_len + adjusted.len().max(original.len()),
526             opad = original.len(),
527         )
528         .into()
529     } else {
530         Markup::fenced_block(&original.display(sema.db))
531     };
532     res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
533     Some(res)
534 }
535 
536 fn closure_ty(
537     sema: &Semantics<'_, RootDatabase>,
538     config: &HoverConfig,
539     TypeInfo { original, adjusted }: &TypeInfo,
540 ) -> Option<HoverResult> {
541     let c = original.as_closure()?;
542     let mut captures_rendered = c.captured_items(sema.db)
543         .into_iter()
544         .map(|it| {
545             let borrow_kind = match it.kind() {
546                 CaptureKind::SharedRef => "immutable borrow",
547                 CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
548                 CaptureKind::MutableRef => "mutable borrow",
549                 CaptureKind::Move => "move",
550             };
551             format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
552         })
553         .join("\n");
554     if captures_rendered.trim().is_empty() {
555         captures_rendered = "This closure captures nothing".to_string();
556     }
557     let mut targets: Vec<hir::ModuleDef> = Vec::new();
558     let mut push_new_def = |item: hir::ModuleDef| {
559         if !targets.contains(&item) {
560             targets.push(item);
561         }
562     };
563     walk_and_push_ty(sema.db, original, &mut push_new_def);
564     c.capture_types(sema.db).into_iter().for_each(|ty| {
565         walk_and_push_ty(sema.db, &ty, &mut push_new_def);
566     });
567 
568     let adjusted = if let Some(adjusted_ty) = adjusted {
569         walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
570         format!(
571             "\nCoerced to: {}",
572             adjusted_ty.display(sema.db).with_closure_style(hir::ClosureStyle::ImplFn)
573         )
574     } else {
575         String::new()
576     };
577     let mut markup = format!("```rust\n{}", c.display_with_id(sema.db),);
578 
579     if let Some(layout) =
580         render_memory_layout(config.memory_layout, || original.layout(sema.db), |_| None, |_| None)
581     {
582         format_to!(markup, "{layout}");
583     }
584     format_to!(
585         markup,
586         "\n{}\n```{adjusted}\n\n## Captures\n{}",
587         c.display_with_impl(sema.db),
588         captures_rendered,
589     );
590 
591     let mut res = HoverResult::default();
592     res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
593     res.markup = markup.into();
594     Some(res)
595 }
596 
render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup>597 fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
598     let name = attr.name(db);
599     let desc = format!("#[{name}]");
600 
601     let AttributeTemplate { word, list, name_value_str } = match attr.template(db) {
602         Some(template) => template,
603         None => return Some(Markup::fenced_block(&attr.name(db))),
604     };
605     let mut docs = "Valid forms are:".to_owned();
606     if word {
607         format_to!(docs, "\n - #\\[{}]", name);
608     }
609     if let Some(list) = list {
610         format_to!(docs, "\n - #\\[{}({})]", name, list);
611     }
612     if let Some(name_value_str) = name_value_str {
613         format_to!(docs, "\n - #\\[{} = {}]", name, name_value_str);
614     }
615     markup(Some(docs.replace('*', "\\*")), desc, None)
616 }
617 
label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>) where D: HasAttrs + HirDisplay,618 fn label_and_docs<D>(db: &RootDatabase, def: D) -> (String, Option<hir::Documentation>)
619 where
620     D: HasAttrs + HirDisplay,
621 {
622     let label = def.display(db).to_string();
623     let docs = def.attrs(db).docs();
624     (label, docs)
625 }
626 
label_and_layout_info_and_docs<D, E, E2>( db: &RootDatabase, def: D, config: &HoverConfig, layout_extractor: E, layout_offset_extractor: E2, ) -> (String, Option<hir::Documentation>) where D: HasAttrs + HirDisplay, E: Fn(&D) -> Result<Layout, LayoutError>, E2: Fn(&Layout) -> Option<u64>,627 fn label_and_layout_info_and_docs<D, E, E2>(
628     db: &RootDatabase,
629     def: D,
630     config: &HoverConfig,
631     layout_extractor: E,
632     layout_offset_extractor: E2,
633 ) -> (String, Option<hir::Documentation>)
634 where
635     D: HasAttrs + HirDisplay,
636     E: Fn(&D) -> Result<Layout, LayoutError>,
637     E2: Fn(&Layout) -> Option<u64>,
638 {
639     let mut label = def.display(db).to_string();
640     if let Some(layout) = render_memory_layout(
641         config.memory_layout,
642         || layout_extractor(&def),
643         layout_offset_extractor,
644         |_| None,
645     ) {
646         format_to!(label, "{layout}");
647     }
648     let docs = def.attrs(db).docs();
649     (label, docs)
650 }
651 
label_value_and_layout_info_and_docs<D, E, E2, E3, V>( db: &RootDatabase, def: D, config: &HoverConfig, value_extractor: E, layout_extractor: E2, layout_tag_extractor: E3, ) -> (String, Option<hir::Documentation>) where D: HasAttrs + HirDisplay, E: Fn(&D) -> Option<V>, E2: Fn(&D) -> Result<Layout, LayoutError>, E3: Fn(&Layout) -> Option<usize>, V: Display,652 fn label_value_and_layout_info_and_docs<D, E, E2, E3, V>(
653     db: &RootDatabase,
654     def: D,
655     config: &HoverConfig,
656     value_extractor: E,
657     layout_extractor: E2,
658     layout_tag_extractor: E3,
659 ) -> (String, Option<hir::Documentation>)
660 where
661     D: HasAttrs + HirDisplay,
662     E: Fn(&D) -> Option<V>,
663     E2: Fn(&D) -> Result<Layout, LayoutError>,
664     E3: Fn(&Layout) -> Option<usize>,
665     V: Display,
666 {
667     let value = value_extractor(&def);
668     let mut label = match value {
669         Some(value) => format!("{} = {value}", def.display(db)),
670         None => def.display(db).to_string(),
671     };
672     if let Some(layout) = render_memory_layout(
673         config.memory_layout,
674         || layout_extractor(&def),
675         |_| None,
676         layout_tag_extractor,
677     ) {
678         format_to!(label, "{layout}");
679     }
680     let docs = def.attrs(db).docs();
681     (label, docs)
682 }
683 
label_value_and_docs<D, E, V>( db: &RootDatabase, def: D, value_extractor: E, ) -> (String, Option<hir::Documentation>) where D: HasAttrs + HirDisplay, E: Fn(&D) -> Option<V>, V: Display,684 fn label_value_and_docs<D, E, V>(
685     db: &RootDatabase,
686     def: D,
687     value_extractor: E,
688 ) -> (String, Option<hir::Documentation>)
689 where
690     D: HasAttrs + HirDisplay,
691     E: Fn(&D) -> Option<V>,
692     V: Display,
693 {
694     let label = if let Some(value) = value_extractor(&def) {
695         format!("{} = {value}", def.display(db))
696     } else {
697         def.display(db).to_string()
698     };
699     let docs = def.attrs(db).docs();
700     (label, docs)
701 }
702 
definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String>703 fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
704     if let Definition::GenericParam(_) = def {
705         return None;
706     }
707     def.module(db).map(|module| path(db, module, definition_owner_name(db, def)))
708 }
709 
markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup>710 fn markup(docs: Option<String>, desc: String, mod_path: Option<String>) -> Option<Markup> {
711     let mut buf = String::new();
712 
713     if let Some(mod_path) = mod_path {
714         if !mod_path.is_empty() {
715             format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
716         }
717     }
718     format_to!(buf, "```rust\n{}\n```", desc);
719 
720     if let Some(doc) = docs {
721         format_to!(buf, "\n___\n\n{}", doc);
722     }
723     Some(buf.into())
724 }
725 
builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Option<Markup>726 fn builtin(famous_defs: &FamousDefs<'_, '_>, builtin: hir::BuiltinType) -> Option<Markup> {
727     // std exposes prim_{} modules with docstrings on the root to document the builtins
728     let primitive_mod = format!("prim_{}", builtin.name().display(famous_defs.0.db));
729     let doc_owner = find_std_module(famous_defs, &primitive_mod)?;
730     let docs = doc_owner.attrs(famous_defs.0.db).docs()?;
731     markup(Some(docs.into()), builtin.name().display(famous_defs.0.db).to_string(), None)
732 }
733 
find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module>734 fn find_std_module(famous_defs: &FamousDefs<'_, '_>, name: &str) -> Option<hir::Module> {
735     let db = famous_defs.0.db;
736     let std_crate = famous_defs.std()?;
737     let std_root_module = std_crate.root_module(db);
738     std_root_module.children(db).find(|module| {
739         module.name(db).map_or(false, |module| module.display(db).to_string() == name)
740     })
741 }
742 
local(db: &RootDatabase, it: hir::Local, config: &HoverConfig) -> Option<Markup>743 fn local(db: &RootDatabase, it: hir::Local, config: &HoverConfig) -> Option<Markup> {
744     let ty = it.ty(db);
745     let ty = ty.display_truncated(db, None);
746     let is_mut = if it.is_mut(db) { "mut " } else { "" };
747     let mut desc = match it.primary_source(db).into_ident_pat() {
748         Some(ident) => {
749             let name = it.name(db);
750             let let_kw = if ident
751                 .syntax()
752                 .parent()
753                 .map_or(false, |p| p.kind() == LET_STMT || p.kind() == LET_EXPR)
754             {
755                 "let "
756             } else {
757                 ""
758             };
759             format!("{let_kw}{is_mut}{}: {ty}", name.display(db))
760         }
761         None => format!("{is_mut}self: {ty}"),
762     };
763     if let Some(layout) =
764         render_memory_layout(config.memory_layout, || it.ty(db).layout(db), |_| None, |_| None)
765     {
766         format_to!(desc, "{layout}");
767     }
768     markup(None, desc, None)
769 }
770 
render_memory_layout( config: Option<MemoryLayoutHoverConfig>, layout: impl FnOnce() -> Result<Layout, LayoutError>, offset: impl FnOnce(&Layout) -> Option<u64>, tag: impl FnOnce(&Layout) -> Option<usize>, ) -> Option<String>771 fn render_memory_layout(
772     config: Option<MemoryLayoutHoverConfig>,
773     layout: impl FnOnce() -> Result<Layout, LayoutError>,
774     offset: impl FnOnce(&Layout) -> Option<u64>,
775     tag: impl FnOnce(&Layout) -> Option<usize>,
776 ) -> Option<String> {
777     // field
778 
779     let config = config?;
780     let layout = layout().ok()?;
781 
782     let mut label = String::from(" // ");
783 
784     if let Some(render) = config.size {
785         let size = match tag(&layout) {
786             Some(tag) => layout.size() as usize - tag,
787             None => layout.size() as usize,
788         };
789         format_to!(label, "size = ");
790         match render {
791             MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{size}"),
792             MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{size:#X}"),
793             MemoryLayoutHoverRenderKind::Both if size >= 10 => {
794                 format_to!(label, "{size} ({size:#X})")
795             }
796             MemoryLayoutHoverRenderKind::Both => format_to!(label, "{size}"),
797         }
798         format_to!(label, ", ");
799     }
800 
801     if let Some(render) = config.alignment {
802         let align = layout.align();
803         format_to!(label, "align = ");
804         match render {
805             MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{align}",),
806             MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{align:#X}",),
807             MemoryLayoutHoverRenderKind::Both if align >= 10 => {
808                 format_to!(label, "{align} ({align:#X})")
809             }
810             MemoryLayoutHoverRenderKind::Both => {
811                 format_to!(label, "{align}")
812             }
813         }
814         format_to!(label, ", ");
815     }
816 
817     if let Some(render) = config.offset {
818         if let Some(offset) = offset(&layout) {
819             format_to!(label, "offset = ");
820             match render {
821                 MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{offset}"),
822                 MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{offset:#X}"),
823                 MemoryLayoutHoverRenderKind::Both if offset >= 10 => {
824                     format_to!(label, "{offset} ({offset:#X})")
825                 }
826                 MemoryLayoutHoverRenderKind::Both => {
827                     format_to!(label, "{offset}")
828                 }
829             }
830             format_to!(label, ", ");
831         }
832     }
833 
834     if config.niches {
835         if let Some(niches) = layout.niches() {
836             format_to!(label, "niches = {niches}, ");
837         }
838     }
839     label.pop(); // ' '
840     label.pop(); // ','
841     Some(label)
842 }
843 
844 struct KeywordHint {
845     description: String,
846     keyword_mod: String,
847     actions: Vec<HoverAction>,
848 }
849 
850 impl KeywordHint {
new(description: String, keyword_mod: String) -> Self851     fn new(description: String, keyword_mod: String) -> Self {
852         Self { description, keyword_mod, actions: Vec::default() }
853     }
854 }
855 
keyword_hints( sema: &Semantics<'_, RootDatabase>, token: &SyntaxToken, parent: syntax::SyntaxNode, ) -> KeywordHint856 fn keyword_hints(
857     sema: &Semantics<'_, RootDatabase>,
858     token: &SyntaxToken,
859     parent: syntax::SyntaxNode,
860 ) -> KeywordHint {
861     match token.kind() {
862         T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
863             let keyword_mod = format!("{}_keyword", token.text());
864 
865             match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
866                 // ignore the unit type ()
867                 Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
868                     let mut targets: Vec<hir::ModuleDef> = Vec::new();
869                     let mut push_new_def = |item: hir::ModuleDef| {
870                         if !targets.contains(&item) {
871                             targets.push(item);
872                         }
873                     };
874                     walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
875 
876                     let ty = ty.adjusted();
877                     let description = format!("{}: {}", token.text(), ty.display(sema.db));
878 
879                     KeywordHint {
880                         description,
881                         keyword_mod,
882                         actions: vec![HoverAction::goto_type_from_targets(sema.db, targets)],
883                     }
884                 }
885                 _ => KeywordHint {
886                     description: token.text().to_string(),
887                     keyword_mod,
888                     actions: Vec::new(),
889                 },
890             }
891         }
892         T![fn] => {
893             let module = match ast::FnPtrType::cast(parent) {
894                 // treat fn keyword inside function pointer type as primitive
895                 Some(_) => format!("prim_{}", token.text()),
896                 None => format!("{}_keyword", token.text()),
897             };
898             KeywordHint::new(token.text().to_string(), module)
899         }
900         T![Self] => KeywordHint::new(token.text().to_string(), "self_upper_keyword".into()),
901         _ => KeywordHint::new(token.text().to_string(), format!("{}_keyword", token.text())),
902     }
903 }
904