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