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