ide/hover/
render.rs

1//! Logic for rendering the different hover messages
2use std::{borrow::Cow, env, mem, ops::Not};
3
4use either::Either;
5use hir::{
6    Adt, AsAssocItem, AsExternAssocItem, CaptureKind, DisplayTarget, DropGlue,
7    DynCompatibilityViolation, HasCrate, HasSource, HirDisplay, Layout, LayoutError,
8    MethodViolationCode, Name, Semantics, Symbol, Trait, Type, TypeInfo, VariantDef,
9    db::ExpandDatabase,
10};
11use ide_db::{
12    RootDatabase,
13    defs::{Definition, find_std_module},
14    documentation::{Documentation, HasDocs},
15    famous_defs::FamousDefs,
16    generated::lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
17    syntax_helpers::prettify_macro_expansion,
18};
19use itertools::Itertools;
20use rustc_apfloat::{
21    Float,
22    ieee::{Half as f16, Quad as f128},
23};
24use span::{Edition, TextSize};
25use stdx::format_to;
26use syntax::{AstNode, AstToken, Direction, SyntaxToken, T, algo, ast, match_ast};
27
28use crate::{
29    HoverAction, HoverConfig, HoverResult, Markup, MemoryLayoutHoverConfig,
30    MemoryLayoutHoverRenderKind,
31    doc_links::{remove_links, rewrite_links},
32    hover::{SubstTyLen, notable_traits, walk_and_push_ty},
33    interpret::render_const_eval_error,
34};
35
36pub(super) fn type_info_of(
37    sema: &Semantics<'_, RootDatabase>,
38    _config: &HoverConfig<'_>,
39    expr_or_pat: &Either<ast::Expr, ast::Pat>,
40    edition: Edition,
41    display_target: DisplayTarget,
42) -> Option<HoverResult> {
43    let ty_info = match expr_or_pat {
44        Either::Left(expr) => sema.type_of_expr(expr)?,
45        Either::Right(pat) => sema.type_of_pat(pat)?,
46    };
47    type_info(sema, _config, ty_info, edition, display_target)
48}
49
50pub(super) fn closure_expr(
51    sema: &Semantics<'_, RootDatabase>,
52    config: &HoverConfig<'_>,
53    c: ast::ClosureExpr,
54    edition: Edition,
55    display_target: DisplayTarget,
56) -> Option<HoverResult> {
57    let TypeInfo { original, .. } = sema.type_of_expr(&c.into())?;
58    closure_ty(sema, config, &TypeInfo { original, adjusted: None }, edition, display_target)
59}
60
61pub(super) fn try_expr(
62    sema: &Semantics<'_, RootDatabase>,
63    _config: &HoverConfig<'_>,
64    try_expr: &ast::TryExpr,
65    edition: Edition,
66    display_target: DisplayTarget,
67) -> Option<HoverResult> {
68    let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
69    let mut ancestors = try_expr.syntax().ancestors();
70    let mut body_ty = loop {
71        let next = ancestors.next()?;
72        break match_ast! {
73            match next {
74                ast::Fn(fn_) => sema.to_def(&fn_)?.ret_type(sema.db),
75                ast::Item(__) => return None,
76                ast::ClosureExpr(closure) => sema.type_of_expr(&closure.body()?)?.original,
77                ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) {
78                    sema.type_of_expr(&block_expr.into())?.original
79                } else {
80                    continue;
81                },
82                _ => continue,
83            }
84        };
85    };
86
87    if inner_ty == body_ty {
88        return None;
89    }
90
91    let mut inner_ty = inner_ty;
92    let mut s = "Try Target".to_owned();
93
94    let adts = inner_ty.as_adt().zip(body_ty.as_adt());
95    if let Some((hir::Adt::Enum(inner), hir::Adt::Enum(body))) = adts {
96        let famous_defs = FamousDefs(sema, sema.scope(try_expr.syntax())?.krate());
97        // special case for two options, there is no value in showing them
98        if let Some(option_enum) = famous_defs.core_option_Option()
99            && inner == option_enum
100            && body == option_enum
101        {
102            cov_mark::hit!(hover_try_expr_opt_opt);
103            return None;
104        }
105
106        // special case two results to show the error variants only
107        if let Some(result_enum) = famous_defs.core_result_Result()
108            && inner == result_enum
109            && body == result_enum
110        {
111            let error_type_args =
112                inner_ty.type_arguments().nth(1).zip(body_ty.type_arguments().nth(1));
113            if let Some((inner, body)) = error_type_args {
114                inner_ty = inner;
115                body_ty = body;
116                "Try Error".clone_into(&mut s);
117            }
118        }
119    }
120
121    let mut res = HoverResult::default();
122
123    let mut targets: Vec<hir::ModuleDef> = Vec::new();
124    let mut push_new_def = |item: hir::ModuleDef| {
125        if !targets.contains(&item) {
126            targets.push(item);
127        }
128    };
129    walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
130    walk_and_push_ty(sema.db, &body_ty, &mut push_new_def);
131    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
132        res.actions.push(actions);
133    }
134
135    let inner_ty = inner_ty.display(sema.db, display_target).to_string();
136    let body_ty = body_ty.display(sema.db, display_target).to_string();
137    let ty_len_max = inner_ty.len().max(body_ty.len());
138
139    let l = "Propagated as: ".len() - " Type: ".len();
140    let static_text_len_diff = l as isize - s.len() as isize;
141    let tpad = static_text_len_diff.max(0) as usize;
142    let ppad = static_text_len_diff.min(0).unsigned_abs();
143
144    res.markup = format!(
145        "```text\n{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n```\n",
146        s,
147        inner_ty,
148        body_ty,
149        pad0 = ty_len_max + tpad,
150        pad1 = ty_len_max + ppad,
151    )
152    .into();
153    Some(res)
154}
155
156pub(super) fn deref_expr(
157    sema: &Semantics<'_, RootDatabase>,
158    _config: &HoverConfig<'_>,
159    deref_expr: &ast::PrefixExpr,
160    edition: Edition,
161    display_target: DisplayTarget,
162) -> Option<HoverResult> {
163    let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
164    let TypeInfo { original, adjusted } =
165        sema.type_of_expr(&ast::Expr::from(deref_expr.clone()))?;
166
167    let mut res = HoverResult::default();
168    let mut targets: Vec<hir::ModuleDef> = Vec::new();
169    let mut push_new_def = |item: hir::ModuleDef| {
170        if !targets.contains(&item) {
171            targets.push(item);
172        }
173    };
174    walk_and_push_ty(sema.db, &inner_ty, &mut push_new_def);
175    walk_and_push_ty(sema.db, &original, &mut push_new_def);
176
177    res.markup = if let Some(adjusted_ty) = adjusted {
178        walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
179        let original = original.display(sema.db, display_target).to_string();
180        let adjusted = adjusted_ty.display(sema.db, display_target).to_string();
181        let inner = inner_ty.display(sema.db, display_target).to_string();
182        let type_len = "To type: ".len();
183        let coerced_len = "Coerced to: ".len();
184        let deref_len = "Dereferenced from: ".len();
185        let max_len = (original.len() + type_len)
186            .max(adjusted.len() + coerced_len)
187            .max(inner.len() + deref_len);
188        format!(
189            "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
190            inner,
191            original,
192            adjusted,
193            ipad = max_len - deref_len,
194            apad = max_len - type_len,
195            opad = max_len - coerced_len,
196        )
197        .into()
198    } else {
199        let original = original.display(sema.db, display_target).to_string();
200        let inner = inner_ty.display(sema.db, display_target).to_string();
201        let type_len = "To type: ".len();
202        let deref_len = "Dereferenced from: ".len();
203        let max_len = (original.len() + type_len).max(inner.len() + deref_len);
204        format!(
205            "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\n```\n",
206            inner,
207            original,
208            ipad = max_len - deref_len,
209            apad = max_len - type_len,
210        )
211        .into()
212    };
213    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
214        res.actions.push(actions);
215    }
216
217    Some(res)
218}
219
220pub(super) fn underscore(
221    sema: &Semantics<'_, RootDatabase>,
222    config: &HoverConfig<'_>,
223    token: &SyntaxToken,
224    edition: Edition,
225    display_target: DisplayTarget,
226) -> Option<HoverResult> {
227    if token.kind() != T![_] {
228        return None;
229    }
230    let parent = token.parent()?;
231    let _it = match_ast! {
232        match parent {
233            ast::InferType(it) => it,
234            ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it)),edition, display_target),
235            ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it)),edition, display_target),
236            _ => return None,
237        }
238    };
239    // let it = infer_type.syntax().parent()?;
240    // match_ast! {
241    //     match it {
242    //         ast::LetStmt(_it) => (),
243    //         ast::Param(_it) => (),
244    //         ast::RetType(_it) => (),
245    //         ast::TypeArg(_it) => (),
246
247    //         ast::CastExpr(_it) => (),
248    //         ast::ParenType(_it) => (),
249    //         ast::TupleType(_it) => (),
250    //         ast::PtrType(_it) => (),
251    //         ast::RefType(_it) => (),
252    //         ast::ArrayType(_it) => (),
253    //         ast::SliceType(_it) => (),
254    //         ast::ForType(_it) => (),
255    //         _ => return None,
256    //     }
257    // }
258
259    // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
260    // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
261    None
262}
263
264pub(super) fn keyword(
265    sema: &Semantics<'_, RootDatabase>,
266    config: &HoverConfig<'_>,
267    token: &SyntaxToken,
268    edition: Edition,
269    display_target: DisplayTarget,
270) -> Option<HoverResult> {
271    if !token.kind().is_keyword(edition) || !config.documentation || !config.keywords {
272        return None;
273    }
274    let parent = token.parent()?;
275    let famous_defs = FamousDefs(sema, sema.scope(&parent)?.krate());
276
277    let KeywordHint { description, keyword_mod, actions } =
278        keyword_hints(sema, token, parent, edition, display_target);
279
280    let doc_owner = find_std_module(&famous_defs, &keyword_mod, edition)?;
281    let docs = doc_owner.docs_with_rangemap(sema.db)?;
282    let (markup, range_map) =
283        markup(Some(Either::Left(docs)), description, None, None, String::new());
284    let markup = process_markup(sema.db, Definition::Module(doc_owner), &markup, range_map, config);
285    Some(HoverResult { markup, actions })
286}
287
288/// Returns missing types in a record pattern.
289/// Only makes sense when there's a rest pattern in the record pattern.
290/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
291pub(super) fn struct_rest_pat(
292    sema: &Semantics<'_, RootDatabase>,
293    _config: &HoverConfig<'_>,
294    pattern: &ast::RecordPat,
295    edition: Edition,
296    display_target: DisplayTarget,
297) -> HoverResult {
298    let missing_fields = sema.record_pattern_missing_fields(pattern);
299
300    // if there are no missing fields, the end result is a hover that shows ".."
301    // should be left in to indicate that there are no more fields in the pattern
302    // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
303
304    let mut res = HoverResult::default();
305    let mut targets: Vec<hir::ModuleDef> = Vec::new();
306    let mut push_new_def = |item: hir::ModuleDef| {
307        if !targets.contains(&item) {
308            targets.push(item);
309        }
310    };
311    for (_, t) in &missing_fields {
312        walk_and_push_ty(sema.db, t, &mut push_new_def);
313    }
314
315    res.markup = {
316        let mut s = String::from(".., ");
317        for (f, _) in &missing_fields {
318            s += f.display(sema.db, display_target).to_string().as_ref();
319            s += ", ";
320        }
321        // get rid of trailing comma
322        s.truncate(s.len() - 2);
323
324        Markup::fenced_block(&s)
325    };
326    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
327        res.actions.push(actions);
328    }
329    res
330}
331
332pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
333    let (path, tt) = attr.as_simple_call()?;
334    if !tt.syntax().text_range().contains(token.text_range().start()) {
335        return None;
336    }
337    let (is_clippy, lints) = match &*path {
338        "feature" => (false, FEATURES),
339        "allow" | "deny" | "expect" | "forbid" | "warn" => {
340            let is_clippy = algo::non_trivia_sibling(token.clone().into(), Direction::Prev)
341                .filter(|t| t.kind() == T![:])
342                .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
343                .filter(|t| t.kind() == T![:])
344                .and_then(|t| algo::non_trivia_sibling(t, Direction::Prev))
345                .is_some_and(|t| {
346                    t.kind() == T![ident] && t.into_token().is_some_and(|t| t.text() == "clippy")
347                });
348            if is_clippy { (true, CLIPPY_LINTS) } else { (false, DEFAULT_LINTS) }
349        }
350        _ => return None,
351    };
352
353    let tmp;
354    let needle = if is_clippy {
355        tmp = format!("clippy::{}", token.text());
356        &tmp
357    } else {
358        token.text()
359    };
360
361    let lint =
362        lints.binary_search_by_key(&needle, |lint| lint.label).ok().map(|idx| &lints[idx])?;
363    Some(HoverResult {
364        markup: Markup::from(format!("```\n{}\n```\n---\n\n{}", lint.label, lint.description)),
365        ..Default::default()
366    })
367}
368
369pub(super) fn process_markup(
370    db: &RootDatabase,
371    def: Definition,
372    markup: &Markup,
373    markup_range_map: Option<hir::Docs>,
374    config: &HoverConfig<'_>,
375) -> Markup {
376    let markup = markup.as_str();
377    let markup = if config.links_in_hover {
378        rewrite_links(db, markup, def, markup_range_map.as_ref())
379    } else {
380        remove_links(markup)
381    };
382    Markup::from(markup)
383}
384
385fn definition_owner_name(db: &RootDatabase, def: Definition, edition: Edition) -> Option<String> {
386    match def {
387        Definition::Field(f) => {
388            let parent = f.parent_def(db);
389            let parent_name = parent.name(db);
390            let parent_name = parent_name.display(db, edition).to_string();
391            return match parent {
392                VariantDef::Variant(variant) => {
393                    let enum_name = variant.parent_enum(db).name(db);
394                    Some(format!("{}::{parent_name}", enum_name.display(db, edition)))
395                }
396                _ => Some(parent_name),
397            };
398        }
399        Definition::Variant(e) => Some(e.parent_enum(db).name(db)),
400        Definition::GenericParam(generic_param) => match generic_param.parent() {
401            hir::GenericDef::Adt(it) => Some(it.name(db)),
402            hir::GenericDef::Trait(it) => Some(it.name(db)),
403            hir::GenericDef::TypeAlias(it) => Some(it.name(db)),
404
405            hir::GenericDef::Impl(i) => i.self_ty(db).as_adt().map(|adt| adt.name(db)),
406            hir::GenericDef::Function(it) => {
407                let container = it.as_assoc_item(db).and_then(|assoc| match assoc.container(db) {
408                    hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
409                    hir::AssocItemContainer::Impl(i) => {
410                        i.self_ty(db).as_adt().map(|adt| adt.name(db))
411                    }
412                });
413                match container {
414                    Some(name) => {
415                        return Some(format!(
416                            "{}::{}",
417                            name.display(db, edition),
418                            it.name(db).display(db, edition)
419                        ));
420                    }
421                    None => Some(it.name(db)),
422                }
423            }
424            hir::GenericDef::Const(it) => {
425                let container = it.as_assoc_item(db).and_then(|assoc| match assoc.container(db) {
426                    hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
427                    hir::AssocItemContainer::Impl(i) => {
428                        i.self_ty(db).as_adt().map(|adt| adt.name(db))
429                    }
430                });
431                match container {
432                    Some(name) => {
433                        return Some(format!(
434                            "{}::{}",
435                            name.display(db, edition),
436                            it.name(db)?.display(db, edition)
437                        ));
438                    }
439                    None => it.name(db),
440                }
441            }
442            hir::GenericDef::Static(it) => Some(it.name(db)),
443        },
444        Definition::DeriveHelper(derive_helper) => Some(derive_helper.derive().name(db)),
445        d => {
446            if let Some(assoc_item) = d.as_assoc_item(db) {
447                match assoc_item.container(db) {
448                    hir::AssocItemContainer::Trait(t) => Some(t.name(db)),
449                    hir::AssocItemContainer::Impl(i) => {
450                        i.self_ty(db).as_adt().map(|adt| adt.name(db))
451                    }
452                }
453            } else {
454                return d.as_extern_assoc_item(db).map(|_| "<extern>".to_owned());
455            }
456        }
457    }
458    .map(|name| name.display(db, edition).to_string())
459}
460
461pub(super) fn path(
462    db: &RootDatabase,
463    module: hir::Module,
464    item_name: Option<String>,
465    edition: Edition,
466) -> String {
467    let crate_name = module.krate(db).display_name(db).as_ref().map(|it| it.to_string());
468    let module_path = module
469        .path_to_root(db)
470        .into_iter()
471        .rev()
472        .flat_map(|it| it.name(db).map(|name| name.display(db, edition).to_string()));
473    crate_name.into_iter().chain(module_path).chain(item_name).join("::")
474}
475
476pub(super) fn definition(
477    db: &RootDatabase,
478    def: Definition,
479    famous_defs: Option<&FamousDefs<'_, '_>>,
480    notable_traits: &[(Trait, Vec<(Option<Type<'_>>, Name)>)],
481    macro_arm: Option<u32>,
482    render_extras: bool,
483    subst_types: Option<&Vec<(Symbol, Type<'_>)>>,
484    config: &HoverConfig<'_>,
485    edition: Edition,
486    display_target: DisplayTarget,
487) -> (Markup, Option<hir::Docs>) {
488    let mod_path = definition_path(db, &def, edition);
489    let label = match def {
490        Definition::Trait(trait_) => trait_
491            .display_limited(db, config.max_trait_assoc_items_count, display_target)
492            .to_string(),
493        Definition::Adt(adt @ (Adt::Struct(_) | Adt::Union(_))) => {
494            adt.display_limited(db, config.max_fields_count, display_target).to_string()
495        }
496        Definition::Variant(variant) => {
497            variant.display_limited(db, config.max_fields_count, display_target).to_string()
498        }
499        Definition::Adt(adt @ Adt::Enum(_)) => {
500            adt.display_limited(db, config.max_enum_variants_count, display_target).to_string()
501        }
502        Definition::SelfType(impl_def) => {
503            let self_ty = &impl_def.self_ty(db);
504            match self_ty.as_adt() {
505                Some(adt) => {
506                    adt.display_limited(db, config.max_fields_count, display_target).to_string()
507                }
508                None => self_ty.display(db, display_target).to_string(),
509            }
510        }
511        Definition::Macro(it) => {
512            let mut label = it.display(db, display_target).to_string();
513            if let Some(macro_arm) = macro_arm {
514                format_to!(label, " // matched arm #{}", macro_arm);
515            }
516            label
517        }
518        Definition::Function(fn_) => {
519            fn_.display_with_container_bounds(db, true, display_target).to_string()
520        }
521        _ => def.label(db, display_target),
522    };
523    let docs = def.docs_with_rangemap(db, famous_defs, display_target);
524    let value = || match def {
525        Definition::Variant(it) => {
526            if !it.parent_enum(db).is_data_carrying(db) {
527                match it.eval(db) {
528                    Ok(it) => {
529                        Some(if it >= 10 { format!("{it} ({it:#X})") } else { format!("{it}") })
530                    }
531                    Err(err) => {
532                        let res = it.value(db).map(|it| format!("{it:?}"));
533                        if env::var_os("RA_DEV").is_some() {
534                            let res = res.as_deref().unwrap_or("");
535                            Some(format!(
536                                "{res} ({})",
537                                render_const_eval_error(db, err, display_target)
538                            ))
539                        } else {
540                            res
541                        }
542                    }
543                }
544            } else {
545                None
546            }
547        }
548        Definition::Const(it) => {
549            let body = it.eval(db);
550            Some(match body {
551                Ok(it) => match it.render_debug(db) {
552                    Ok(it) => it,
553                    Err(err) => {
554                        let it = it.render(db, display_target);
555                        if env::var_os("RA_DEV").is_some() {
556                            format!(
557                                "{it}\n{}",
558                                render_const_eval_error(db, err.into(), display_target)
559                            )
560                        } else {
561                            it
562                        }
563                    }
564                },
565                Err(err) => {
566                    let source = it.source(db)?;
567                    let mut body = source.value.body()?.syntax().clone();
568                    if let Some(macro_file) = source.file_id.macro_file() {
569                        let span_map = db.expansion_span_map(macro_file);
570                        body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
571                    }
572                    if env::var_os("RA_DEV").is_some() {
573                        format!("{body}\n{}", render_const_eval_error(db, err, display_target))
574                    } else {
575                        body.to_string()
576                    }
577                }
578            })
579        }
580        Definition::Static(it) => {
581            let body = it.eval(db);
582            Some(match body {
583                Ok(it) => match it.render_debug(db) {
584                    Ok(it) => it,
585                    Err(err) => {
586                        let it = it.render(db, display_target);
587                        if env::var_os("RA_DEV").is_some() {
588                            format!(
589                                "{it}\n{}",
590                                render_const_eval_error(db, err.into(), display_target)
591                            )
592                        } else {
593                            it
594                        }
595                    }
596                },
597                Err(err) => {
598                    let source = it.source(db)?;
599                    let mut body = source.value.body()?.syntax().clone();
600                    if let Some(macro_file) = source.file_id.macro_file() {
601                        let span_map = db.expansion_span_map(macro_file);
602                        body = prettify_macro_expansion(db, body, &span_map, it.krate(db).into());
603                    }
604                    if env::var_os("RA_DEV").is_some() {
605                        format!("{body}\n{}", render_const_eval_error(db, err, display_target))
606                    } else {
607                        body.to_string()
608                    }
609                }
610            })
611        }
612        _ => None,
613    };
614
615    let layout_info = || match def {
616        Definition::Field(it) => render_memory_layout(
617            config.memory_layout,
618            || it.layout(db),
619            |_| {
620                let var_def = it.parent_def(db);
621                match var_def {
622                    hir::VariantDef::Struct(s) => {
623                        Adt::from(s).layout(db).ok().and_then(|layout| layout.field_offset(it))
624                    }
625                    _ => None,
626                }
627            },
628            |_| None,
629            |_| None,
630        ),
631        Definition::Adt(it @ Adt::Struct(strukt)) => render_memory_layout(
632            config.memory_layout,
633            || it.layout(db),
634            |_| None,
635            |layout| {
636                let mut field_size =
637                    |i: usize| Some(strukt.fields(db).get(i)?.layout(db).ok()?.size());
638                if strukt.repr(db).is_some_and(|it| it.inhibit_struct_field_reordering()) {
639                    Some(("tail padding", layout.tail_padding(&mut field_size)?))
640                } else {
641                    Some(("largest padding", layout.largest_padding(&mut field_size)?))
642                }
643            },
644            |_| None,
645        ),
646        Definition::Adt(it) => render_memory_layout(
647            config.memory_layout,
648            || it.layout(db),
649            |_| None,
650            |_| None,
651            |_| None,
652        ),
653        Definition::Variant(it) => render_memory_layout(
654            config.memory_layout,
655            || it.layout(db),
656            |_| None,
657            |_| None,
658            |layout| layout.enum_tag_size(),
659        ),
660        Definition::TypeAlias(it) => render_memory_layout(
661            config.memory_layout,
662            || it.ty(db).layout(db),
663            |_| None,
664            |_| None,
665            |_| None,
666        ),
667        Definition::Local(it) => render_memory_layout(
668            config.memory_layout,
669            || it.ty(db).layout(db),
670            |_| None,
671            |_| None,
672            |_| None,
673        ),
674        Definition::SelfType(it) => render_memory_layout(
675            config.memory_layout,
676            || it.self_ty(db).layout(db),
677            |_| None,
678            |_| None,
679            |_| None,
680        ),
681        _ => None,
682    };
683
684    let drop_info = || {
685        if !config.show_drop_glue {
686            return None;
687        }
688        let drop_info = match def {
689            Definition::Field(field) => {
690                DropInfo { drop_glue: field.ty(db).to_type(db).drop_glue(db), has_dtor: None }
691            }
692            Definition::Adt(Adt::Struct(strukt)) => {
693                let struct_drop_glue = strukt.ty_params(db).drop_glue(db);
694                let mut fields_drop_glue = strukt
695                    .fields(db)
696                    .iter()
697                    .map(|field| field.ty(db).to_type(db).drop_glue(db))
698                    .max()
699                    .unwrap_or(DropGlue::None);
700                let has_dtor = match (fields_drop_glue, struct_drop_glue) {
701                    (DropGlue::None, _) => struct_drop_glue != DropGlue::None,
702                    (_, DropGlue::None) => {
703                        // This is `ManuallyDrop`.
704                        fields_drop_glue = DropGlue::None;
705                        false
706                    }
707                    (_, _) => struct_drop_glue > fields_drop_glue,
708                };
709                DropInfo { drop_glue: fields_drop_glue, has_dtor: Some(has_dtor) }
710            }
711            // Unions cannot have fields with drop glue.
712            Definition::Adt(Adt::Union(union)) => DropInfo {
713                drop_glue: DropGlue::None,
714                has_dtor: Some(union.ty_params(db).drop_glue(db) != DropGlue::None),
715            },
716            Definition::Adt(Adt::Enum(enum_)) => {
717                let enum_drop_glue = enum_.ty_params(db).drop_glue(db);
718                let fields_drop_glue = enum_
719                    .variants(db)
720                    .iter()
721                    .map(|variant| {
722                        variant
723                            .fields(db)
724                            .iter()
725                            .map(|field| field.ty(db).to_type(db).drop_glue(db))
726                            .max()
727                            .unwrap_or(DropGlue::None)
728                    })
729                    .max()
730                    .unwrap_or(DropGlue::None);
731                DropInfo {
732                    drop_glue: fields_drop_glue,
733                    has_dtor: Some(enum_drop_glue > fields_drop_glue),
734                }
735            }
736            Definition::Variant(variant) => {
737                let fields_drop_glue = variant
738                    .fields(db)
739                    .iter()
740                    .map(|field| field.ty(db).to_type(db).drop_glue(db))
741                    .max()
742                    .unwrap_or(DropGlue::None);
743                DropInfo { drop_glue: fields_drop_glue, has_dtor: None }
744            }
745            Definition::TypeAlias(type_alias) => {
746                DropInfo { drop_glue: type_alias.ty_params(db).drop_glue(db), has_dtor: None }
747            }
748            Definition::Local(local) => {
749                DropInfo { drop_glue: local.ty(db).drop_glue(db), has_dtor: None }
750            }
751            _ => return None,
752        };
753        let rendered_drop_glue = if drop_info.has_dtor == Some(true) {
754            "impl Drop"
755        } else {
756            match drop_info.drop_glue {
757                DropGlue::HasDropGlue => "needs Drop",
758                DropGlue::None => "no Drop",
759                DropGlue::DependOnParams => "type param may need Drop",
760            }
761        };
762
763        Some(rendered_drop_glue.to_owned())
764    };
765
766    let dyn_compatibility_info = || match def {
767        Definition::Trait(it) => {
768            let mut dyn_compatibility_info = String::new();
769            render_dyn_compatibility(db, &mut dyn_compatibility_info, it.dyn_compatibility(db));
770            Some(dyn_compatibility_info)
771        }
772        _ => None,
773    };
774
775    let variance_info = || match def {
776        Definition::GenericParam(it) => it.variance(db).as_ref().map(ToString::to_string),
777        _ => None,
778    };
779
780    let mut extra = String::new();
781    if render_extras {
782        if let Some(notable_traits) =
783            render_notable_trait(db, notable_traits, edition, display_target)
784        {
785            extra.push_str("\n___\n");
786            extra.push_str(&notable_traits);
787        }
788        if let Some(variance_info) = variance_info() {
789            extra.push_str("\n___\n");
790            extra.push_str(&variance_info);
791        }
792        if let Some(layout_info) = layout_info() {
793            extra.push_str("\n___\n");
794            extra.push_str(&layout_info);
795            if let Some(drop_info) = drop_info() {
796                extra.push_str(", ");
797                extra.push_str(&drop_info)
798            }
799        } else if let Some(drop_info) = drop_info() {
800            extra.push_str("\n___\n");
801            extra.push_str(&drop_info);
802        }
803        if let Some(dyn_compatibility_info) = dyn_compatibility_info() {
804            extra.push_str("\n___\n");
805            extra.push_str(&dyn_compatibility_info);
806        }
807    }
808    let mut desc = String::new();
809    desc.push_str(&label);
810    if let Some(value) = value() {
811        desc.push_str(" = ");
812        desc.push_str(&value);
813    }
814
815    let subst_types = match config.max_subst_ty_len {
816        SubstTyLen::Hide => String::new(),
817        SubstTyLen::LimitTo(_) | SubstTyLen::Unlimited => {
818            let limit = if let SubstTyLen::LimitTo(limit) = config.max_subst_ty_len {
819                Some(limit)
820            } else {
821                None
822            };
823            subst_types
824                .map(|subst_type| {
825                    subst_type
826                        .iter()
827                        .filter(|(_, ty)| !ty.is_unknown())
828                        .format_with(", ", |(name, ty), fmt| {
829                            fmt(&format_args!(
830                                "`{name}` = `{}`",
831                                ty.display_truncated(db, limit, display_target)
832                            ))
833                        })
834                        .to_string()
835                })
836                .unwrap_or_default()
837        }
838    };
839
840    markup(docs, desc, extra.is_empty().not().then_some(extra), mod_path, subst_types)
841}
842
843#[derive(Debug)]
844struct DropInfo {
845    drop_glue: DropGlue,
846    has_dtor: Option<bool>,
847}
848
849pub(super) fn literal(
850    sema: &Semantics<'_, RootDatabase>,
851    token: SyntaxToken,
852    display_target: DisplayTarget,
853) -> Option<Markup> {
854    let lit = token.parent().and_then(ast::Literal::cast)?;
855    let ty = if let Some(p) = lit.syntax().parent().and_then(ast::Pat::cast) {
856        sema.type_of_pat(&p)?
857    } else {
858        sema.type_of_expr(&ast::Expr::Literal(lit))?
859    }
860    .original;
861
862    let value = match_ast! {
863        match token {
864            ast::String(string)     => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
865            ast::ByteString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("{it:?}")),
866            ast::CString(string)    => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| std::str::from_utf8(it).map_or_else(|e| format!("{e:?}"), ToOwned::to_owned)),
867            ast::Char(char)         => char  .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
868            ast::Byte(byte)         => byte  .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")),
869            ast::FloatNumber(num) => {
870                let text = num.value_string();
871                if ty.as_builtin().map(|it| it.is_f16()).unwrap_or(false) {
872                    match text.parse::<f16>() {
873                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
874                        Err(e) => Err(e.0.to_owned()),
875                    }
876                } else if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) {
877                    match text.parse::<f32>() {
878                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
879                        Err(e) => Err(e.to_string()),
880                    }
881                } else if ty.as_builtin().map(|it| it.is_f128()).unwrap_or(false) {
882                    match text.parse::<f128>() {
883                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
884                        Err(e) => Err(e.0.to_owned()),
885                    }
886                } else {
887                    match text.parse::<f64>() {
888                        Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
889                        Err(e) => Err(e.to_string()),
890                    }
891                }
892            },
893            ast::IntNumber(num) => match num.value() {
894                Ok(num) => Ok(format!("{num} (0x{num:X}|0b{num:b})")),
895                Err(e) => Err(e.to_string()),
896            },
897            _ => return None
898        }
899    };
900    let ty = ty.display(sema.db, display_target);
901
902    let mut s = format!("```rust\n{ty}\n```\n---\n\n");
903    match value {
904        Ok(value) => {
905            let backtick_len = value.chars().filter(|c| *c == '`').count();
906            let spaces_len = value.chars().filter(|c| *c == ' ').count();
907            let backticks = "`".repeat(backtick_len + 1);
908            let space_char = if spaces_len == value.len() { "" } else { " " };
909
910            if let Some(newline) = value.find('\n') {
911                format_to!(
912                    s,
913                    "value of literal (truncated up to newline): {backticks}{space_char}{}{space_char}{backticks}",
914                    &value[..newline]
915                )
916            } else {
917                format_to!(
918                    s,
919                    "value of literal: {backticks}{space_char}{value}{space_char}{backticks}"
920                )
921            }
922        }
923        Err(error) => format_to!(s, "invalid literal: {error}"),
924    }
925    Some(s.into())
926}
927
928fn render_notable_trait(
929    db: &RootDatabase,
930    notable_traits: &[(Trait, Vec<(Option<Type<'_>>, Name)>)],
931    edition: Edition,
932    display_target: DisplayTarget,
933) -> Option<String> {
934    let mut desc = String::new();
935    let mut needs_impl_header = true;
936    for (trait_, assoc_types) in notable_traits {
937        desc.push_str(if mem::take(&mut needs_impl_header) {
938            "Implements notable traits: `"
939        } else {
940            "`, `"
941        });
942        format_to!(desc, "{}", trait_.name(db).display(db, edition));
943        if !assoc_types.is_empty() {
944            desc.push('<');
945            format_to!(
946                desc,
947                "{}",
948                assoc_types.iter().format_with(", ", |(ty, name), f| {
949                    f(&name.display(db, edition))?;
950                    f(&" = ")?;
951                    match ty {
952                        Some(ty) => f(&ty.display(db, display_target)),
953                        None => f(&"?"),
954                    }
955                })
956            );
957            desc.push('>');
958        }
959    }
960    if desc.is_empty() {
961        None
962    } else {
963        desc.push('`');
964        Some(desc)
965    }
966}
967
968fn type_info(
969    sema: &Semantics<'_, RootDatabase>,
970    config: &HoverConfig<'_>,
971    ty: TypeInfo<'_>,
972    edition: Edition,
973    display_target: DisplayTarget,
974) -> Option<HoverResult> {
975    if let Some(res) = closure_ty(sema, config, &ty, edition, display_target) {
976        return Some(res);
977    };
978    let db = sema.db;
979    let TypeInfo { original, adjusted } = ty;
980    let mut res = HoverResult::default();
981    let mut targets: Vec<hir::ModuleDef> = Vec::new();
982    let mut push_new_def = |item: hir::ModuleDef| {
983        if !targets.contains(&item) {
984            targets.push(item);
985        }
986    };
987    walk_and_push_ty(db, &original, &mut push_new_def);
988
989    res.markup = if let Some(adjusted_ty) = adjusted {
990        walk_and_push_ty(db, &adjusted_ty, &mut push_new_def);
991
992        let notable = if let Some(notable) =
993            render_notable_trait(db, &notable_traits(db, &original), edition, display_target)
994        {
995            format!("{notable}\n")
996        } else {
997            String::new()
998        };
999
1000        let original = original.display(db, display_target).to_string();
1001        let adjusted = adjusted_ty.display(db, display_target).to_string();
1002        let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
1003        format!(
1004            "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n{notable}```\n",
1005            original,
1006            adjusted,
1007            apad = static_text_diff_len + adjusted.len().max(original.len()),
1008            opad = original.len(),
1009        )
1010        .into()
1011    } else {
1012        let mut desc = format!("```rust\n{}\n```", original.display(db, display_target));
1013        if let Some(extra) =
1014            render_notable_trait(db, &notable_traits(db, &original), edition, display_target)
1015        {
1016            desc.push_str("\n---\n");
1017            desc.push_str(&extra);
1018        };
1019        desc.into()
1020    };
1021    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
1022        res.actions.push(actions);
1023    }
1024    Some(res)
1025}
1026
1027fn closure_ty(
1028    sema: &Semantics<'_, RootDatabase>,
1029    config: &HoverConfig<'_>,
1030    TypeInfo { original, adjusted }: &TypeInfo<'_>,
1031    edition: Edition,
1032    display_target: DisplayTarget,
1033) -> Option<HoverResult> {
1034    let c = original.as_closure()?;
1035    let mut captures_rendered = c.captured_items(sema.db)
1036        .into_iter()
1037        .map(|it| {
1038            let borrow_kind = match it.kind() {
1039                CaptureKind::SharedRef => "immutable borrow",
1040                CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
1041                CaptureKind::MutableRef => "mutable borrow",
1042                CaptureKind::Move => "move",
1043            };
1044            format!("* `{}` by {}", it.display_place(sema.db), borrow_kind)
1045        })
1046        .join("\n");
1047    if captures_rendered.trim().is_empty() {
1048        "This closure captures nothing".clone_into(&mut captures_rendered);
1049    }
1050    let mut targets: Vec<hir::ModuleDef> = Vec::new();
1051    let mut push_new_def = |item: hir::ModuleDef| {
1052        if !targets.contains(&item) {
1053            targets.push(item);
1054        }
1055    };
1056    walk_and_push_ty(sema.db, original, &mut push_new_def);
1057    c.capture_types(sema.db).into_iter().for_each(|ty| {
1058        walk_and_push_ty(sema.db, &ty, &mut push_new_def);
1059    });
1060
1061    let adjusted = if let Some(adjusted_ty) = adjusted {
1062        walk_and_push_ty(sema.db, adjusted_ty, &mut push_new_def);
1063        format!(
1064            "\nCoerced to: {}",
1065            adjusted_ty
1066                .display(sema.db, display_target)
1067                .with_closure_style(hir::ClosureStyle::ImplFn)
1068        )
1069    } else {
1070        String::new()
1071    };
1072    let mut markup = format!("```rust\n{}\n```", c.display_with_impl(sema.db, display_target));
1073
1074    if let Some(trait_) = c.fn_trait(sema.db).get_id(sema.db, original.krate(sema.db)) {
1075        push_new_def(trait_.into())
1076    }
1077    if let Some(layout) = render_memory_layout(
1078        config.memory_layout,
1079        || original.layout(sema.db),
1080        |_| None,
1081        |_| None,
1082        |_| None,
1083    ) {
1084        format_to!(markup, "\n---\n{layout}");
1085    }
1086    format_to!(markup, "{adjusted}\n\n## Captures\n{}", captures_rendered,);
1087
1088    let mut res = HoverResult::default();
1089    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
1090        res.actions.push(actions);
1091    }
1092    res.markup = markup.into();
1093    Some(res)
1094}
1095
1096fn definition_path(db: &RootDatabase, &def: &Definition, edition: Edition) -> Option<String> {
1097    if matches!(
1098        def,
1099        Definition::TupleField(_)
1100            | Definition::Label(_)
1101            | Definition::Local(_)
1102            | Definition::BuiltinAttr(_)
1103            | Definition::BuiltinLifetime(_)
1104            | Definition::BuiltinType(_)
1105            | Definition::InlineAsmRegOrRegClass(_)
1106            | Definition::InlineAsmOperand(_)
1107    ) {
1108        return None;
1109    }
1110    let rendered_parent = definition_owner_name(db, def, edition);
1111    def.module(db).map(|module| path(db, module, rendered_parent, edition))
1112}
1113
1114fn markup(
1115    docs: Option<Either<Cow<'_, hir::Docs>, Documentation<'_>>>,
1116    rust: String,
1117    extra: Option<String>,
1118    mod_path: Option<String>,
1119    subst_types: String,
1120) -> (Markup, Option<hir::Docs>) {
1121    let mut buf = String::new();
1122
1123    if let Some(mod_path) = mod_path
1124        && !mod_path.is_empty()
1125    {
1126        format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
1127    }
1128    format_to!(buf, "```rust\n{}\n```", rust);
1129
1130    if let Some(extra) = extra {
1131        buf.push_str(&extra);
1132    }
1133
1134    if !subst_types.is_empty() {
1135        format_to!(buf, "\n___\n{subst_types}");
1136    }
1137
1138    if let Some(doc) = docs {
1139        format_to!(buf, "\n___\n\n");
1140        let offset = TextSize::new(buf.len() as u32);
1141        let docs_str = match &doc {
1142            Either::Left(docs) => docs.docs(),
1143            Either::Right(docs) => docs.as_str(),
1144        };
1145        format_to!(buf, "{}", docs_str);
1146        let range_map = match doc {
1147            Either::Left(range_map) => {
1148                let mut range_map = range_map.into_owned();
1149                range_map.shift_by(offset);
1150                Some(range_map)
1151            }
1152            Either::Right(_) => None,
1153        };
1154
1155        (buf.into(), range_map)
1156    } else {
1157        (buf.into(), None)
1158    }
1159}
1160
1161fn render_memory_layout(
1162    config: Option<MemoryLayoutHoverConfig>,
1163    layout: impl FnOnce() -> Result<Layout, LayoutError>,
1164    offset: impl FnOnce(&Layout) -> Option<u64>,
1165    padding: impl FnOnce(&Layout) -> Option<(&str, u64)>,
1166    tag: impl FnOnce(&Layout) -> Option<usize>,
1167) -> Option<String> {
1168    let config = config?;
1169    let layout = layout().ok()?;
1170
1171    let mut label = String::new();
1172
1173    if let Some(render) = config.size {
1174        let size = match tag(&layout) {
1175            Some(tag) => layout.size() as usize - tag,
1176            None => layout.size() as usize,
1177        };
1178        format_to!(label, "size = ");
1179        match render {
1180            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{size}"),
1181            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{size:#X}"),
1182            MemoryLayoutHoverRenderKind::Both if size >= 10 => {
1183                format_to!(label, "{size} ({size:#X})")
1184            }
1185            MemoryLayoutHoverRenderKind::Both => format_to!(label, "{size}"),
1186        }
1187        format_to!(label, ", ");
1188    }
1189
1190    if let Some(render) = config.alignment {
1191        let align = layout.align();
1192        format_to!(label, "align = ");
1193        match render {
1194            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{align}",),
1195            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{align:#X}",),
1196            MemoryLayoutHoverRenderKind::Both if align >= 10 => {
1197                format_to!(label, "{align} ({align:#X})")
1198            }
1199            MemoryLayoutHoverRenderKind::Both => {
1200                format_to!(label, "{align}")
1201            }
1202        }
1203        format_to!(label, ", ");
1204    }
1205
1206    if let Some(render) = config.offset
1207        && let Some(offset) = offset(&layout)
1208    {
1209        format_to!(label, "offset = ");
1210        match render {
1211            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{offset}"),
1212            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{offset:#X}"),
1213            MemoryLayoutHoverRenderKind::Both if offset >= 10 => {
1214                format_to!(label, "{offset} ({offset:#X})")
1215            }
1216            MemoryLayoutHoverRenderKind::Both => {
1217                format_to!(label, "{offset}")
1218            }
1219        }
1220        format_to!(label, ", ");
1221    }
1222
1223    if let Some(render) = config.padding
1224        && let Some((padding_name, padding)) = padding(&layout)
1225    {
1226        format_to!(label, "{padding_name} = ");
1227        match render {
1228            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{padding}"),
1229            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{padding:#X}"),
1230            MemoryLayoutHoverRenderKind::Both if padding >= 10 => {
1231                format_to!(label, "{padding} ({padding:#X})")
1232            }
1233            MemoryLayoutHoverRenderKind::Both => {
1234                format_to!(label, "{padding}")
1235            }
1236        }
1237        format_to!(label, ", ");
1238    }
1239
1240    if config.niches
1241        && let Some(niches) = layout.niches()
1242    {
1243        if niches > 1024 {
1244            if niches.is_power_of_two() {
1245                format_to!(label, "niches = 2{}, ", pwr2_to_exponent(niches));
1246            } else if is_pwr2plus1(niches) {
1247                format_to!(label, "niches = 2{} + 1, ", pwr2_to_exponent(niches - 1));
1248            } else if is_pwr2minus1(niches) {
1249                format_to!(label, "niches = 2{} - 1, ", pwr2_to_exponent(niches + 1));
1250            } else {
1251                format_to!(label, "niches = a lot, ");
1252            }
1253        } else {
1254            format_to!(label, "niches = {niches}, ");
1255        }
1256    }
1257    label.pop(); // ' '
1258    label.pop(); // ','
1259    Some(label)
1260}
1261
1262struct KeywordHint {
1263    description: String,
1264    keyword_mod: String,
1265    actions: Vec<HoverAction>,
1266}
1267
1268impl KeywordHint {
1269    fn new(description: String, keyword_mod: String) -> Self {
1270        Self { description, keyword_mod, actions: Vec::default() }
1271    }
1272}
1273
1274fn keyword_hints(
1275    sema: &Semantics<'_, RootDatabase>,
1276    token: &SyntaxToken,
1277    parent: syntax::SyntaxNode,
1278    edition: Edition,
1279    display_target: DisplayTarget,
1280) -> KeywordHint {
1281    match token.kind() {
1282        T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
1283            let keyword_mod = format!("{}_keyword", token.text());
1284
1285            match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
1286                // ignore the unit type ()
1287                Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
1288                    let mut targets: Vec<hir::ModuleDef> = Vec::new();
1289                    let mut push_new_def = |item: hir::ModuleDef| {
1290                        if !targets.contains(&item) {
1291                            targets.push(item);
1292                        }
1293                    };
1294                    walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
1295
1296                    let ty = ty.adjusted();
1297                    let description =
1298                        format!("{}: {}", token.text(), ty.display(sema.db, display_target));
1299
1300                    KeywordHint {
1301                        description,
1302                        keyword_mod,
1303                        actions: HoverAction::goto_type_from_targets(sema, targets, edition)
1304                            .into_iter()
1305                            .collect(),
1306                    }
1307                }
1308                _ => KeywordHint {
1309                    description: token.text().to_owned(),
1310                    keyword_mod,
1311                    actions: Vec::new(),
1312                },
1313            }
1314        }
1315        T![fn] => {
1316            let module = match ast::FnPtrType::cast(parent) {
1317                // treat fn keyword inside function pointer type as primitive
1318                Some(_) => format!("prim_{}", token.text()),
1319                None => format!("{}_keyword", token.text()),
1320            };
1321            KeywordHint::new(token.text().to_owned(), module)
1322        }
1323        T![Self] => KeywordHint::new(token.text().to_owned(), "self_upper_keyword".into()),
1324        _ => KeywordHint::new(token.text().to_owned(), format!("{}_keyword", token.text())),
1325    }
1326}
1327
1328fn render_dyn_compatibility(
1329    db: &RootDatabase,
1330    buf: &mut String,
1331    safety: Option<DynCompatibilityViolation>,
1332) {
1333    let Some(osv) = safety else {
1334        buf.push_str("Is dyn-compatible");
1335        return;
1336    };
1337    buf.push_str("Is not dyn-compatible due to ");
1338    match osv {
1339        DynCompatibilityViolation::SizedSelf => {
1340            buf.push_str("having a `Self: Sized` bound");
1341        }
1342        DynCompatibilityViolation::SelfReferential => {
1343            buf.push_str("having a bound that references `Self`");
1344        }
1345        DynCompatibilityViolation::Method(func, mvc) => {
1346            let name = hir::Function::from(func).name(db);
1347            format_to!(buf, "having a method `{}` that is not dispatchable due to ", name.as_str());
1348            let desc = match mvc {
1349                MethodViolationCode::StaticMethod => "missing a receiver",
1350                MethodViolationCode::ReferencesSelfInput => "having a parameter referencing `Self`",
1351                MethodViolationCode::ReferencesSelfOutput => "the return type referencing `Self`",
1352                MethodViolationCode::ReferencesImplTraitInTrait => {
1353                    "the return type containing `impl Trait`"
1354                }
1355                MethodViolationCode::AsyncFn => "being async",
1356                MethodViolationCode::WhereClauseReferencesSelf => {
1357                    "a where clause referencing `Self`"
1358                }
1359                MethodViolationCode::Generic => "having a const or type generic parameter",
1360                MethodViolationCode::UndispatchableReceiver => {
1361                    "having a non-dispatchable receiver type"
1362                }
1363            };
1364            buf.push_str(desc);
1365        }
1366        DynCompatibilityViolation::AssocConst(const_) => {
1367            let name = hir::Const::from(const_).name(db);
1368            if let Some(name) = name {
1369                format_to!(buf, "having an associated constant `{}`", name.as_str());
1370            } else {
1371                buf.push_str("having an associated constant");
1372            }
1373        }
1374        DynCompatibilityViolation::GAT(alias) => {
1375            let name = hir::TypeAlias::from(alias).name(db);
1376            format_to!(buf, "having a generic associated type `{}`", name.as_str());
1377        }
1378        DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait) => {
1379            let name = hir::Trait::from(super_trait).name(db);
1380            format_to!(buf, "having a dyn-incompatible supertrait `{}`", name.as_str());
1381        }
1382    }
1383}
1384
1385fn is_pwr2minus1(val: u128) -> bool {
1386    val == u128::MAX || (val + 1).is_power_of_two()
1387}
1388
1389fn is_pwr2plus1(val: u128) -> bool {
1390    val != 0 && (val - 1).is_power_of_two()
1391}
1392
1393/// Formats a power of two as an exponent of two, i.e. 16 => ⁴. Note that `num` MUST be a power
1394/// of 2, or this function will panic.
1395fn pwr2_to_exponent(num: u128) -> String {
1396    const DIGITS: [char; 10] = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1397    assert_eq!(num.count_ones(), 1);
1398    num.trailing_zeros()
1399        .to_string()
1400        .chars()
1401        .map(|c| c.to_digit(10).unwrap() as usize)
1402        .map(|idx| DIGITS[idx])
1403        .collect::<String>()
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408    use super::*;
1409
1410    const TESTERS: [u128; 10] = [0, 1, 2, 3, 4, 255, 256, 257, u128::MAX - 1, u128::MAX];
1411
1412    #[test]
1413    fn test_is_pwr2minus1() {
1414        const OUTCOMES: [bool; 10] =
1415            [true, true, false, true, false, true, false, false, false, true];
1416        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1417            let actual = is_pwr2minus1(*test);
1418            assert_eq!(actual, expected, "is_pwr2minu1({test}) gave {actual}, expected {expected}");
1419        }
1420    }
1421
1422    #[test]
1423    fn test_is_pwr2plus1() {
1424        const OUTCOMES: [bool; 10] =
1425            [false, false, true, true, false, false, false, true, false, false];
1426        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1427            let actual = is_pwr2plus1(*test);
1428            assert_eq!(actual, expected, "is_pwr2plus1({test}) gave {actual}, expected {expected}");
1429        }
1430    }
1431
1432    #[test]
1433    fn test_pwr2_to_exponent() {
1434        const TESTERS: [u128; 9] = [
1435            1,
1436            2,
1437            4,
1438            8,
1439            16,
1440            9223372036854775808,
1441            18446744073709551616,
1442            36893488147419103232,
1443            170141183460469231731687303715884105728,
1444        ];
1445        const OUTCOMES: [&str; 9] = ["⁰", "¹", "²", "³", "⁴", "⁶³", "⁶⁴", "⁶⁵", "¹²⁷"];
1446        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1447            let actual = pwr2_to_exponent(*test);
1448            assert_eq!(
1449                actual, expected,
1450                "pwr2_to_exponent({test}) returned {actual}, expected {expected}",
1451            );
1452        }
1453    }
1454}