Skip to main content

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