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| it.to_string());
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 parse_float = |text: &str| {
846        if ty.as_builtin().map(|it| it.is_f16()).unwrap_or(false) {
847            match text.parse::<f16>() {
848                Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
849                Err(e) => Err(e.0.to_owned()),
850            }
851        } else if ty.as_builtin().map(|it| it.is_f32()).unwrap_or(false) {
852            match text.parse::<f32>() {
853                Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
854                Err(e) => Err(e.to_string()),
855            }
856        } else if ty.as_builtin().map(|it| it.is_f128()).unwrap_or(false) {
857            match text.parse::<f128>() {
858                Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
859                Err(e) => Err(e.0.to_owned()),
860            }
861        } else {
862            match text.parse::<f64>() {
863                Ok(num) => Ok(format!("{num} (bits: 0x{:X})", num.to_bits())),
864                Err(e) => Err(e.to_string()),
865            }
866        }
867    };
868    let value = match_ast! {
869        match token {
870            ast::String(string)     => string.value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
871            ast::ByteString(string) => string.value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("{it:?}")),
872            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)),
873            ast::Char(char)         => char  .value().as_ref().map_err(|e| format!("{e:?}")).map(ToString::to_string),
874            ast::Byte(byte)         => byte  .value().as_ref().map_err(|e| format!("{e:?}")).map(|it| format!("0x{it:X}")),
875            ast::FloatNumber(num) => parse_float(&num.value_string()),
876            ast::IntNumber(num) if matches!(num.suffix(), Some("f16" | "f32" | "f64" | "f128")) => {
877                parse_float(&num.value_string())
878            },
879            ast::IntNumber(num) => match num.value() {
880                Ok(num) => Ok(format!("{num} (0x{num:X}|0b{num:b})")),
881                Err(e) => Err(e.to_string()),
882            },
883            _ => return None
884        }
885    };
886    let ty = ty.display(sema.db, display_target);
887
888    let mut s = format!("```rust\n{ty}\n```\n---\n\n");
889    match value {
890        Ok(value) => {
891            let backtick_len = value.chars().filter(|c| *c == '`').count();
892            let spaces_len = value.chars().filter(|c| *c == ' ').count();
893            let backticks = "`".repeat(backtick_len + 1);
894            let space_char = if spaces_len == value.len() { "" } else { " " };
895
896            if let Some(newline) = value.find('\n') {
897                format_to!(
898                    s,
899                    "value of literal (truncated up to newline): {backticks}{space_char}{}{space_char}{backticks}",
900                    &value[..newline]
901                )
902            } else {
903                format_to!(
904                    s,
905                    "value of literal: {backticks}{space_char}{value}{space_char}{backticks}"
906                )
907            }
908        }
909        Err(error) => format_to!(s, "invalid literal: {error}"),
910    }
911    Some(s.into())
912}
913
914fn render_notable_trait(
915    db: &RootDatabase,
916    notable_traits: &[(Trait, Vec<(Option<Type<'_>>, Name)>)],
917    edition: Edition,
918    display_target: DisplayTarget,
919) -> Option<String> {
920    let mut desc = String::new();
921    let mut needs_impl_header = true;
922    for (trait_, assoc_types) in notable_traits {
923        desc.push_str(if mem::take(&mut needs_impl_header) {
924            "Implements notable traits: `"
925        } else {
926            "`, `"
927        });
928        format_to!(desc, "{}", trait_.name(db).display(db, edition));
929        if !assoc_types.is_empty() {
930            desc.push('<');
931            format_to!(
932                desc,
933                "{}",
934                assoc_types.iter().format_with(", ", |(ty, name), f| {
935                    f(&name.display(db, edition))?;
936                    f(&" = ")?;
937                    match ty {
938                        Some(ty) => f(&ty.display(db, display_target)),
939                        None => f(&"?"),
940                    }
941                })
942            );
943            desc.push('>');
944        }
945    }
946    if desc.is_empty() {
947        None
948    } else {
949        desc.push('`');
950        Some(desc)
951    }
952}
953
954fn type_info(
955    sema: &Semantics<'_, RootDatabase>,
956    config: &HoverConfig<'_>,
957    ty: TypeInfo<'_>,
958    edition: Edition,
959    display_target: DisplayTarget,
960) -> Option<HoverResult> {
961    if let Some(res) = closure_ty(sema, config, &ty, edition, display_target) {
962        return Some(res);
963    };
964    let db = sema.db;
965    let TypeInfo { original, adjusted } = ty;
966    let mut res = HoverResult::default();
967    let mut targets: Vec<hir::ModuleDef> = Vec::new();
968    let mut push_new_def = |item: hir::ModuleDef| {
969        if !targets.contains(&item) {
970            targets.push(item);
971        }
972    };
973    walk_and_push_ty(db, &original, &mut push_new_def);
974
975    res.markup = if let Some(adjusted_ty) = adjusted {
976        walk_and_push_ty(db, &adjusted_ty, &mut push_new_def);
977
978        let notable = if let Some(notable) =
979            render_notable_trait(db, &notable_traits(db, &original), edition, display_target)
980        {
981            format!("{notable}\n")
982        } else {
983            String::new()
984        };
985
986        let original = original.display(db, display_target).to_string();
987        let adjusted = adjusted_ty.display(db, display_target).to_string();
988        let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
989        format!(
990            "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n{notable}```\n",
991            original,
992            adjusted,
993            apad = static_text_diff_len + adjusted.len().max(original.len()),
994            opad = original.len(),
995        )
996        .into()
997    } else {
998        let mut desc = format!("```rust\n{}\n```", original.display(db, display_target));
999        if let Some(extra) =
1000            render_notable_trait(db, &notable_traits(db, &original), edition, display_target)
1001        {
1002            desc.push_str("\n---\n");
1003            desc.push_str(&extra);
1004        };
1005        desc.into()
1006    };
1007    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
1008        res.actions.push(actions);
1009    }
1010    Some(res)
1011}
1012
1013fn closure_ty(
1014    sema: &Semantics<'_, RootDatabase>,
1015    config: &HoverConfig<'_>,
1016    TypeInfo { original, adjusted }: &TypeInfo<'_>,
1017    edition: Edition,
1018    display_target: DisplayTarget,
1019) -> Option<HoverResult> {
1020    let c = original.as_closure()?;
1021    let captures = c.captured_items(sema.db);
1022    let mut captures_rendered = captures
1023        .iter()
1024        .map(|it| {
1025            let borrow_kind = match it.kind() {
1026                CaptureKind::SharedRef => "immutable borrow",
1027                CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
1028                CaptureKind::MutableRef => "mutable borrow",
1029                CaptureKind::Move => "move",
1030            };
1031            format!("* `{}` by {}", it.display_place_source_code(sema.db, display_target.edition), borrow_kind)
1032        })
1033        .join("\n");
1034    if captures_rendered.trim().is_empty() {
1035        "This closure captures nothing".clone_into(&mut captures_rendered);
1036    }
1037    let mut targets: Vec<hir::ModuleDef> = Vec::new();
1038    let mut push_new_def = |item: hir::ModuleDef| {
1039        if !targets.contains(&item) {
1040            targets.push(item);
1041        }
1042    };
1043    walk_and_push_ty(sema.db, original, &mut push_new_def);
1044    captures.iter().for_each(|capture| {
1045        walk_and_push_ty(sema.db, &capture.ty(sema.db), &mut push_new_def);
1046    });
1047
1048    let adjusted = if let Some(adjusted_ty) = adjusted {
1049        walk_and_push_ty(sema.db, adjusted_ty, &mut push_new_def);
1050        format!(
1051            "\nCoerced to: {}",
1052            adjusted_ty
1053                .display(sema.db, display_target)
1054                .with_closure_style(hir::ClosureStyle::ImplFn)
1055        )
1056    } else {
1057        String::new()
1058    };
1059    let mut markup = format!("```rust\n{}\n```", c.display_with_impl(sema.db, display_target));
1060
1061    if let Some(trait_) = c.fn_trait(sema.db).get_id(sema.db, original.krate(sema.db)) {
1062        push_new_def(trait_.into())
1063    }
1064    if let Some(layout) = render_memory_layout(
1065        config.memory_layout,
1066        || original.layout(sema.db),
1067        |_| None,
1068        |_| None,
1069        |_| None,
1070    ) {
1071        format_to!(markup, "\n---\n{layout}");
1072    }
1073    format_to!(markup, "{adjusted}\n\n## Captures\n{}", captures_rendered,);
1074
1075    let mut res = HoverResult::default();
1076    if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
1077        res.actions.push(actions);
1078    }
1079    res.markup = markup.into();
1080    Some(res)
1081}
1082
1083fn definition_path(db: &RootDatabase, &def: &Definition<'_>, edition: Edition) -> Option<String> {
1084    if matches!(
1085        def,
1086        Definition::TupleField(_)
1087            | Definition::Label(_)
1088            | Definition::Local(_)
1089            | Definition::BuiltinAttr(_)
1090            | Definition::BuiltinLifetime(_)
1091            | Definition::BuiltinType(_)
1092            | Definition::InlineAsmRegOrRegClass(_)
1093            | Definition::InlineAsmOperand(_)
1094    ) {
1095        return None;
1096    }
1097    let rendered_parent = definition_owner_name(db, def, edition);
1098    def.module(db).map(|module| path(db, module, rendered_parent, edition))
1099}
1100
1101fn markup(
1102    docs: Option<Either<Cow<'_, hir::Docs>, Documentation<'_>>>,
1103    rust: String,
1104    extra: Option<String>,
1105    mod_path: Option<String>,
1106    subst_types: String,
1107) -> (Markup, Option<hir::Docs>) {
1108    let mut buf = String::new();
1109
1110    if let Some(mod_path) = mod_path
1111        && !mod_path.is_empty()
1112    {
1113        format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
1114    }
1115    format_to!(buf, "```rust\n{}\n```", rust);
1116
1117    if let Some(extra) = extra {
1118        buf.push_str(&extra);
1119    }
1120
1121    if !subst_types.is_empty() {
1122        format_to!(buf, "\n___\n{subst_types}");
1123    }
1124
1125    if let Some(doc) = docs {
1126        format_to!(buf, "\n___\n\n");
1127        let offset = TextSize::new(buf.len() as u32);
1128        let docs_str = match &doc {
1129            Either::Left(docs) => docs.docs(),
1130            Either::Right(docs) => docs.as_str(),
1131        };
1132        format_to!(buf, "{}", docs_str);
1133        let range_map = match doc {
1134            Either::Left(range_map) => {
1135                let mut range_map = range_map.into_owned();
1136                range_map.shift_by(offset);
1137                Some(range_map)
1138            }
1139            Either::Right(_) => None,
1140        };
1141
1142        (buf.into(), range_map)
1143    } else {
1144        (buf.into(), None)
1145    }
1146}
1147
1148fn render_memory_layout<'db>(
1149    config: Option<MemoryLayoutHoverConfig>,
1150    layout: impl FnOnce() -> Result<Layout<'db>, LayoutError>,
1151    offset: impl FnOnce(&Layout<'db>) -> Option<u64>,
1152    padding: impl for<'a> FnOnce(&'a Layout<'db>) -> Option<(&'a str, u64)>,
1153    tag: impl FnOnce(&Layout<'db>) -> Option<usize>,
1154) -> Option<String> {
1155    let config = config?;
1156    let layout = layout().ok()?;
1157
1158    let mut label = String::new();
1159
1160    if let Some(render) = config.size {
1161        let size = match tag(&layout) {
1162            Some(tag) => layout.size() as usize - tag,
1163            None => layout.size() as usize,
1164        };
1165        format_to!(label, "size = ");
1166        match render {
1167            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{size}"),
1168            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{size:#X}"),
1169            MemoryLayoutHoverRenderKind::Both if size >= 10 => {
1170                format_to!(label, "{size} ({size:#X})")
1171            }
1172            MemoryLayoutHoverRenderKind::Both => format_to!(label, "{size}"),
1173        }
1174        format_to!(label, ", ");
1175    }
1176
1177    if let Some(render) = config.alignment {
1178        let align = layout.align();
1179        format_to!(label, "align = ");
1180        match render {
1181            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{align}",),
1182            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{align:#X}",),
1183            MemoryLayoutHoverRenderKind::Both if align >= 10 => {
1184                format_to!(label, "{align} ({align:#X})")
1185            }
1186            MemoryLayoutHoverRenderKind::Both => {
1187                format_to!(label, "{align}")
1188            }
1189        }
1190        format_to!(label, ", ");
1191    }
1192
1193    if let Some(render) = config.offset
1194        && let Some(offset) = offset(&layout)
1195    {
1196        format_to!(label, "offset = ");
1197        match render {
1198            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{offset}"),
1199            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{offset:#X}"),
1200            MemoryLayoutHoverRenderKind::Both if offset >= 10 => {
1201                format_to!(label, "{offset} ({offset:#X})")
1202            }
1203            MemoryLayoutHoverRenderKind::Both => {
1204                format_to!(label, "{offset}")
1205            }
1206        }
1207        format_to!(label, ", ");
1208    }
1209
1210    if let Some(render) = config.padding
1211        && let Some((padding_name, padding)) = padding(&layout)
1212    {
1213        format_to!(label, "{padding_name} = ");
1214        match render {
1215            MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{padding}"),
1216            MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{padding:#X}"),
1217            MemoryLayoutHoverRenderKind::Both if padding >= 10 => {
1218                format_to!(label, "{padding} ({padding:#X})")
1219            }
1220            MemoryLayoutHoverRenderKind::Both => {
1221                format_to!(label, "{padding}")
1222            }
1223        }
1224        format_to!(label, ", ");
1225    }
1226
1227    if config.niches
1228        && let Some(niches) = layout.niches()
1229    {
1230        if niches > 1024 {
1231            if niches.is_power_of_two() {
1232                format_to!(label, "niches = 2{}, ", pwr2_to_exponent(niches));
1233            } else if is_pwr2plus1(niches) {
1234                format_to!(label, "niches = 2{} + 1, ", pwr2_to_exponent(niches - 1));
1235            } else if is_pwr2minus1(niches) {
1236                format_to!(label, "niches = 2{} - 1, ", pwr2_to_exponent(niches + 1));
1237            } else {
1238                format_to!(label, "niches = a lot, ");
1239            }
1240        } else {
1241            format_to!(label, "niches = {niches}, ");
1242        }
1243    }
1244    label.pop(); // ' '
1245    label.pop(); // ','
1246    Some(label)
1247}
1248
1249struct KeywordHint {
1250    description: String,
1251    keyword_mod: String,
1252    actions: Vec<HoverAction>,
1253}
1254
1255impl KeywordHint {
1256    fn new(description: String, keyword_mod: String) -> Self {
1257        Self { description, keyword_mod, actions: Vec::default() }
1258    }
1259}
1260
1261fn keyword_hints(
1262    sema: &Semantics<'_, RootDatabase>,
1263    token: &SyntaxToken,
1264    parent: syntax::SyntaxNode,
1265    edition: Edition,
1266    display_target: DisplayTarget,
1267) -> KeywordHint {
1268    match token.kind() {
1269        T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
1270            let keyword_mod = format!("{}_keyword", token.text());
1271
1272            match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
1273                // ignore the unit type ()
1274                Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
1275                    let mut targets: Vec<hir::ModuleDef> = Vec::new();
1276                    let mut push_new_def = |item: hir::ModuleDef| {
1277                        if !targets.contains(&item) {
1278                            targets.push(item);
1279                        }
1280                    };
1281                    walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
1282
1283                    let ty = ty.adjusted();
1284                    let description =
1285                        format!("{}: {}", token.text(), ty.display(sema.db, display_target));
1286
1287                    KeywordHint {
1288                        description,
1289                        keyword_mod,
1290                        actions: HoverAction::goto_type_from_targets(sema, targets, edition)
1291                            .into_iter()
1292                            .collect(),
1293                    }
1294                }
1295                _ => KeywordHint {
1296                    description: token.text().to_owned(),
1297                    keyword_mod,
1298                    actions: Vec::new(),
1299                },
1300            }
1301        }
1302        T![fn] => {
1303            let module = match ast::FnPtrType::cast(parent) {
1304                // treat fn keyword inside function pointer type as primitive
1305                Some(_) => format!("prim_{}", token.text()),
1306                None => format!("{}_keyword", token.text()),
1307            };
1308            KeywordHint::new(token.text().to_owned(), module)
1309        }
1310        T![Self] => KeywordHint::new(token.text().to_owned(), "self_upper_keyword".into()),
1311        _ => KeywordHint::new(token.text().to_owned(), format!("{}_keyword", token.text())),
1312    }
1313}
1314
1315fn render_dyn_compatibility(
1316    db: &RootDatabase,
1317    buf: &mut String,
1318    safety: Option<DynCompatibilityViolation>,
1319) {
1320    let Some(osv) = safety else {
1321        buf.push_str("Is dyn-compatible");
1322        return;
1323    };
1324    buf.push_str("Is not dyn-compatible due to ");
1325    match osv {
1326        DynCompatibilityViolation::SizedSelf => {
1327            buf.push_str("having a `Self: Sized` bound");
1328        }
1329        DynCompatibilityViolation::SelfReferential => {
1330            buf.push_str("having a bound that references `Self`");
1331        }
1332        DynCompatibilityViolation::Method(func, mvc) => {
1333            let name = hir::Function::from(func).name(db);
1334            format_to!(buf, "having a method `{}` that is not dispatchable due to ", name.as_str());
1335            let desc = match mvc {
1336                MethodViolationCode::StaticMethod => "missing a receiver",
1337                MethodViolationCode::ReferencesSelfInput => "having a parameter referencing `Self`",
1338                MethodViolationCode::ReferencesSelfOutput => "the return type referencing `Self`",
1339                MethodViolationCode::ReferencesImplTraitInTrait => {
1340                    "the return type containing `impl Trait`"
1341                }
1342                MethodViolationCode::AsyncFn => "being async",
1343                MethodViolationCode::WhereClauseReferencesSelf => {
1344                    "a where clause referencing `Self`"
1345                }
1346                MethodViolationCode::Generic => "having a const or type generic parameter",
1347                MethodViolationCode::UndispatchableReceiver => {
1348                    "having a non-dispatchable receiver type"
1349                }
1350            };
1351            buf.push_str(desc);
1352        }
1353        DynCompatibilityViolation::AssocConst(const_) => {
1354            let name = hir::Const::from(const_).name(db);
1355            if let Some(name) = name {
1356                format_to!(buf, "having an associated constant `{}`", name.as_str());
1357            } else {
1358                buf.push_str("having an associated constant");
1359            }
1360        }
1361        DynCompatibilityViolation::GAT(alias) => {
1362            let name = hir::TypeAlias::from(alias).name(db);
1363            format_to!(buf, "having a generic associated type `{}`", name.as_str());
1364        }
1365        DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait) => {
1366            let name = hir::Trait::from(super_trait).name(db);
1367            format_to!(buf, "having a dyn-incompatible supertrait `{}`", name.as_str());
1368        }
1369    }
1370}
1371
1372fn is_pwr2minus1(val: u128) -> bool {
1373    val == u128::MAX || (val + 1).is_power_of_two()
1374}
1375
1376fn is_pwr2plus1(val: u128) -> bool {
1377    val != 0 && (val - 1).is_power_of_two()
1378}
1379
1380/// Formats a power of two as an exponent of two, i.e. 16 => ⁴. Note that `num` MUST be a power
1381/// of 2, or this function will panic.
1382fn pwr2_to_exponent(num: u128) -> String {
1383    const DIGITS: [char; 10] = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1384    assert_eq!(num.count_ones(), 1);
1385    num.trailing_zeros()
1386        .to_string()
1387        .chars()
1388        .map(|c| c.to_digit(10).unwrap() as usize)
1389        .map(|idx| DIGITS[idx])
1390        .collect::<String>()
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395    use super::*;
1396
1397    const TESTERS: [u128; 10] = [0, 1, 2, 3, 4, 255, 256, 257, u128::MAX - 1, u128::MAX];
1398
1399    #[test]
1400    fn test_is_pwr2minus1() {
1401        const OUTCOMES: [bool; 10] =
1402            [true, true, false, true, false, true, false, false, false, true];
1403        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1404            let actual = is_pwr2minus1(*test);
1405            assert_eq!(actual, expected, "is_pwr2minu1({test}) gave {actual}, expected {expected}");
1406        }
1407    }
1408
1409    #[test]
1410    fn test_is_pwr2plus1() {
1411        const OUTCOMES: [bool; 10] =
1412            [false, false, true, true, false, false, false, true, false, false];
1413        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1414            let actual = is_pwr2plus1(*test);
1415            assert_eq!(actual, expected, "is_pwr2plus1({test}) gave {actual}, expected {expected}");
1416        }
1417    }
1418
1419    #[test]
1420    fn test_pwr2_to_exponent() {
1421        const TESTERS: [u128; 9] = [
1422            1,
1423            2,
1424            4,
1425            8,
1426            16,
1427            9223372036854775808,
1428            18446744073709551616,
1429            36893488147419103232,
1430            170141183460469231731687303715884105728,
1431        ];
1432        const OUTCOMES: [&str; 9] = ["⁰", "¹", "²", "³", "⁴", "⁶³", "⁶⁴", "⁶⁵", "¹²⁷"];
1433        for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1434            let actual = pwr2_to_exponent(*test);
1435            assert_eq!(
1436                actual, expected,
1437                "pwr2_to_exponent({test}) returned {actual}, expected {expected}",
1438            );
1439        }
1440    }
1441}