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