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, Variant,
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 Variant::EnumVariant(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::EnumVariant(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::EnumVariant(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::EnumVariant(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::Variant::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::EnumVariant(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::EnumVariant(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 captures = c.captured_items(sema.db);
1013 let mut captures_rendered = captures
1014 .iter()
1015 .map(|it| {
1016 let borrow_kind = match it.kind() {
1017 CaptureKind::SharedRef => "immutable borrow",
1018 CaptureKind::UniqueSharedRef => "unique immutable borrow ([read more](https://doc.rust-lang.org/stable/reference/types/closure.html#unique-immutable-borrows-in-captures))",
1019 CaptureKind::MutableRef => "mutable borrow",
1020 CaptureKind::Move => "move",
1021 };
1022 format!("* `{}` by {}", it.display_place_source_code(sema.db, display_target.edition), borrow_kind)
1023 })
1024 .join("\n");
1025 if captures_rendered.trim().is_empty() {
1026 "This closure captures nothing".clone_into(&mut captures_rendered);
1027 }
1028 let mut targets: Vec<hir::ModuleDef> = Vec::new();
1029 let mut push_new_def = |item: hir::ModuleDef| {
1030 if !targets.contains(&item) {
1031 targets.push(item);
1032 }
1033 };
1034 walk_and_push_ty(sema.db, original, &mut push_new_def);
1035 captures.iter().for_each(|capture| {
1036 walk_and_push_ty(sema.db, &capture.ty(sema.db), &mut push_new_def);
1037 });
1038
1039 let adjusted = if let Some(adjusted_ty) = adjusted {
1040 walk_and_push_ty(sema.db, adjusted_ty, &mut push_new_def);
1041 format!(
1042 "\nCoerced to: {}",
1043 adjusted_ty
1044 .display(sema.db, display_target)
1045 .with_closure_style(hir::ClosureStyle::ImplFn)
1046 )
1047 } else {
1048 String::new()
1049 };
1050 let mut markup = format!("```rust\n{}\n```", c.display_with_impl(sema.db, display_target));
1051
1052 if let Some(trait_) = c.fn_trait(sema.db).get_id(sema.db, original.krate(sema.db)) {
1053 push_new_def(trait_.into())
1054 }
1055 if let Some(layout) = render_memory_layout(
1056 config.memory_layout,
1057 || original.layout(sema.db),
1058 |_| None,
1059 |_| None,
1060 |_| None,
1061 ) {
1062 format_to!(markup, "\n---\n{layout}");
1063 }
1064 format_to!(markup, "{adjusted}\n\n## Captures\n{}", captures_rendered,);
1065
1066 let mut res = HoverResult::default();
1067 if let Some(actions) = HoverAction::goto_type_from_targets(sema, targets, edition) {
1068 res.actions.push(actions);
1069 }
1070 res.markup = markup.into();
1071 Some(res)
1072}
1073
1074fn definition_path(db: &RootDatabase, &def: &Definition, edition: Edition) -> Option<String> {
1075 if matches!(
1076 def,
1077 Definition::TupleField(_)
1078 | Definition::Label(_)
1079 | Definition::Local(_)
1080 | Definition::BuiltinAttr(_)
1081 | Definition::BuiltinLifetime(_)
1082 | Definition::BuiltinType(_)
1083 | Definition::InlineAsmRegOrRegClass(_)
1084 | Definition::InlineAsmOperand(_)
1085 ) {
1086 return None;
1087 }
1088 let rendered_parent = definition_owner_name(db, def, edition);
1089 def.module(db).map(|module| path(db, module, rendered_parent, edition))
1090}
1091
1092fn markup(
1093 docs: Option<Either<Cow<'_, hir::Docs>, Documentation<'_>>>,
1094 rust: String,
1095 extra: Option<String>,
1096 mod_path: Option<String>,
1097 subst_types: String,
1098) -> (Markup, Option<hir::Docs>) {
1099 let mut buf = String::new();
1100
1101 if let Some(mod_path) = mod_path
1102 && !mod_path.is_empty()
1103 {
1104 format_to!(buf, "```rust\n{}\n```\n\n", mod_path);
1105 }
1106 format_to!(buf, "```rust\n{}\n```", rust);
1107
1108 if let Some(extra) = extra {
1109 buf.push_str(&extra);
1110 }
1111
1112 if !subst_types.is_empty() {
1113 format_to!(buf, "\n___\n{subst_types}");
1114 }
1115
1116 if let Some(doc) = docs {
1117 format_to!(buf, "\n___\n\n");
1118 let offset = TextSize::new(buf.len() as u32);
1119 let docs_str = match &doc {
1120 Either::Left(docs) => docs.docs(),
1121 Either::Right(docs) => docs.as_str(),
1122 };
1123 format_to!(buf, "{}", docs_str);
1124 let range_map = match doc {
1125 Either::Left(range_map) => {
1126 let mut range_map = range_map.into_owned();
1127 range_map.shift_by(offset);
1128 Some(range_map)
1129 }
1130 Either::Right(_) => None,
1131 };
1132
1133 (buf.into(), range_map)
1134 } else {
1135 (buf.into(), None)
1136 }
1137}
1138
1139fn render_memory_layout<'db>(
1140 config: Option<MemoryLayoutHoverConfig>,
1141 layout: impl FnOnce() -> Result<Layout<'db>, LayoutError>,
1142 offset: impl FnOnce(&Layout<'db>) -> Option<u64>,
1143 padding: impl for<'a> FnOnce(&'a Layout<'db>) -> Option<(&'a str, u64)>,
1144 tag: impl FnOnce(&Layout<'db>) -> Option<usize>,
1145) -> Option<String> {
1146 let config = config?;
1147 let layout = layout().ok()?;
1148
1149 let mut label = String::new();
1150
1151 if let Some(render) = config.size {
1152 let size = match tag(&layout) {
1153 Some(tag) => layout.size() as usize - tag,
1154 None => layout.size() as usize,
1155 };
1156 format_to!(label, "size = ");
1157 match render {
1158 MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{size}"),
1159 MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{size:#X}"),
1160 MemoryLayoutHoverRenderKind::Both if size >= 10 => {
1161 format_to!(label, "{size} ({size:#X})")
1162 }
1163 MemoryLayoutHoverRenderKind::Both => format_to!(label, "{size}"),
1164 }
1165 format_to!(label, ", ");
1166 }
1167
1168 if let Some(render) = config.alignment {
1169 let align = layout.align();
1170 format_to!(label, "align = ");
1171 match render {
1172 MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{align}",),
1173 MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{align:#X}",),
1174 MemoryLayoutHoverRenderKind::Both if align >= 10 => {
1175 format_to!(label, "{align} ({align:#X})")
1176 }
1177 MemoryLayoutHoverRenderKind::Both => {
1178 format_to!(label, "{align}")
1179 }
1180 }
1181 format_to!(label, ", ");
1182 }
1183
1184 if let Some(render) = config.offset
1185 && let Some(offset) = offset(&layout)
1186 {
1187 format_to!(label, "offset = ");
1188 match render {
1189 MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{offset}"),
1190 MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{offset:#X}"),
1191 MemoryLayoutHoverRenderKind::Both if offset >= 10 => {
1192 format_to!(label, "{offset} ({offset:#X})")
1193 }
1194 MemoryLayoutHoverRenderKind::Both => {
1195 format_to!(label, "{offset}")
1196 }
1197 }
1198 format_to!(label, ", ");
1199 }
1200
1201 if let Some(render) = config.padding
1202 && let Some((padding_name, padding)) = padding(&layout)
1203 {
1204 format_to!(label, "{padding_name} = ");
1205 match render {
1206 MemoryLayoutHoverRenderKind::Decimal => format_to!(label, "{padding}"),
1207 MemoryLayoutHoverRenderKind::Hexadecimal => format_to!(label, "{padding:#X}"),
1208 MemoryLayoutHoverRenderKind::Both if padding >= 10 => {
1209 format_to!(label, "{padding} ({padding:#X})")
1210 }
1211 MemoryLayoutHoverRenderKind::Both => {
1212 format_to!(label, "{padding}")
1213 }
1214 }
1215 format_to!(label, ", ");
1216 }
1217
1218 if config.niches
1219 && let Some(niches) = layout.niches()
1220 {
1221 if niches > 1024 {
1222 if niches.is_power_of_two() {
1223 format_to!(label, "niches = 2{}, ", pwr2_to_exponent(niches));
1224 } else if is_pwr2plus1(niches) {
1225 format_to!(label, "niches = 2{} + 1, ", pwr2_to_exponent(niches - 1));
1226 } else if is_pwr2minus1(niches) {
1227 format_to!(label, "niches = 2{} - 1, ", pwr2_to_exponent(niches + 1));
1228 } else {
1229 format_to!(label, "niches = a lot, ");
1230 }
1231 } else {
1232 format_to!(label, "niches = {niches}, ");
1233 }
1234 }
1235 label.pop(); label.pop(); Some(label)
1238}
1239
1240struct KeywordHint {
1241 description: String,
1242 keyword_mod: String,
1243 actions: Vec<HoverAction>,
1244}
1245
1246impl KeywordHint {
1247 fn new(description: String, keyword_mod: String) -> Self {
1248 Self { description, keyword_mod, actions: Vec::default() }
1249 }
1250}
1251
1252fn keyword_hints(
1253 sema: &Semantics<'_, RootDatabase>,
1254 token: &SyntaxToken,
1255 parent: syntax::SyntaxNode,
1256 edition: Edition,
1257 display_target: DisplayTarget,
1258) -> KeywordHint {
1259 match token.kind() {
1260 T![await] | T![loop] | T![match] | T![unsafe] | T![as] | T![try] | T![if] | T![else] => {
1261 let keyword_mod = format!("{}_keyword", token.text());
1262
1263 match ast::Expr::cast(parent).and_then(|site| sema.type_of_expr(&site)) {
1264 Some(ty) if !ty.adjusted.as_ref().unwrap_or(&ty.original).is_unit() => {
1266 let mut targets: Vec<hir::ModuleDef> = Vec::new();
1267 let mut push_new_def = |item: hir::ModuleDef| {
1268 if !targets.contains(&item) {
1269 targets.push(item);
1270 }
1271 };
1272 walk_and_push_ty(sema.db, &ty.original, &mut push_new_def);
1273
1274 let ty = ty.adjusted();
1275 let description =
1276 format!("{}: {}", token.text(), ty.display(sema.db, display_target));
1277
1278 KeywordHint {
1279 description,
1280 keyword_mod,
1281 actions: HoverAction::goto_type_from_targets(sema, targets, edition)
1282 .into_iter()
1283 .collect(),
1284 }
1285 }
1286 _ => KeywordHint {
1287 description: token.text().to_owned(),
1288 keyword_mod,
1289 actions: Vec::new(),
1290 },
1291 }
1292 }
1293 T![fn] => {
1294 let module = match ast::FnPtrType::cast(parent) {
1295 Some(_) => format!("prim_{}", token.text()),
1297 None => format!("{}_keyword", token.text()),
1298 };
1299 KeywordHint::new(token.text().to_owned(), module)
1300 }
1301 T![Self] => KeywordHint::new(token.text().to_owned(), "self_upper_keyword".into()),
1302 _ => KeywordHint::new(token.text().to_owned(), format!("{}_keyword", token.text())),
1303 }
1304}
1305
1306fn render_dyn_compatibility(
1307 db: &RootDatabase,
1308 buf: &mut String,
1309 safety: Option<DynCompatibilityViolation>,
1310) {
1311 let Some(osv) = safety else {
1312 buf.push_str("Is dyn-compatible");
1313 return;
1314 };
1315 buf.push_str("Is not dyn-compatible due to ");
1316 match osv {
1317 DynCompatibilityViolation::SizedSelf => {
1318 buf.push_str("having a `Self: Sized` bound");
1319 }
1320 DynCompatibilityViolation::SelfReferential => {
1321 buf.push_str("having a bound that references `Self`");
1322 }
1323 DynCompatibilityViolation::Method(func, mvc) => {
1324 let name = hir::Function::from(func).name(db);
1325 format_to!(buf, "having a method `{}` that is not dispatchable due to ", name.as_str());
1326 let desc = match mvc {
1327 MethodViolationCode::StaticMethod => "missing a receiver",
1328 MethodViolationCode::ReferencesSelfInput => "having a parameter referencing `Self`",
1329 MethodViolationCode::ReferencesSelfOutput => "the return type referencing `Self`",
1330 MethodViolationCode::ReferencesImplTraitInTrait => {
1331 "the return type containing `impl Trait`"
1332 }
1333 MethodViolationCode::AsyncFn => "being async",
1334 MethodViolationCode::WhereClauseReferencesSelf => {
1335 "a where clause referencing `Self`"
1336 }
1337 MethodViolationCode::Generic => "having a const or type generic parameter",
1338 MethodViolationCode::UndispatchableReceiver => {
1339 "having a non-dispatchable receiver type"
1340 }
1341 };
1342 buf.push_str(desc);
1343 }
1344 DynCompatibilityViolation::AssocConst(const_) => {
1345 let name = hir::Const::from(const_).name(db);
1346 if let Some(name) = name {
1347 format_to!(buf, "having an associated constant `{}`", name.as_str());
1348 } else {
1349 buf.push_str("having an associated constant");
1350 }
1351 }
1352 DynCompatibilityViolation::GAT(alias) => {
1353 let name = hir::TypeAlias::from(alias).name(db);
1354 format_to!(buf, "having a generic associated type `{}`", name.as_str());
1355 }
1356 DynCompatibilityViolation::HasNonCompatibleSuperTrait(super_trait) => {
1357 let name = hir::Trait::from(super_trait).name(db);
1358 format_to!(buf, "having a dyn-incompatible supertrait `{}`", name.as_str());
1359 }
1360 }
1361}
1362
1363fn is_pwr2minus1(val: u128) -> bool {
1364 val == u128::MAX || (val + 1).is_power_of_two()
1365}
1366
1367fn is_pwr2plus1(val: u128) -> bool {
1368 val != 0 && (val - 1).is_power_of_two()
1369}
1370
1371fn pwr2_to_exponent(num: u128) -> String {
1374 const DIGITS: [char; 10] = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
1375 assert_eq!(num.count_ones(), 1);
1376 num.trailing_zeros()
1377 .to_string()
1378 .chars()
1379 .map(|c| c.to_digit(10).unwrap() as usize)
1380 .map(|idx| DIGITS[idx])
1381 .collect::<String>()
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386 use super::*;
1387
1388 const TESTERS: [u128; 10] = [0, 1, 2, 3, 4, 255, 256, 257, u128::MAX - 1, u128::MAX];
1389
1390 #[test]
1391 fn test_is_pwr2minus1() {
1392 const OUTCOMES: [bool; 10] =
1393 [true, true, false, true, false, true, false, false, false, true];
1394 for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1395 let actual = is_pwr2minus1(*test);
1396 assert_eq!(actual, expected, "is_pwr2minu1({test}) gave {actual}, expected {expected}");
1397 }
1398 }
1399
1400 #[test]
1401 fn test_is_pwr2plus1() {
1402 const OUTCOMES: [bool; 10] =
1403 [false, false, true, true, false, false, false, true, false, false];
1404 for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1405 let actual = is_pwr2plus1(*test);
1406 assert_eq!(actual, expected, "is_pwr2plus1({test}) gave {actual}, expected {expected}");
1407 }
1408 }
1409
1410 #[test]
1411 fn test_pwr2_to_exponent() {
1412 const TESTERS: [u128; 9] = [
1413 1,
1414 2,
1415 4,
1416 8,
1417 16,
1418 9223372036854775808,
1419 18446744073709551616,
1420 36893488147419103232,
1421 170141183460469231731687303715884105728,
1422 ];
1423 const OUTCOMES: [&str; 9] = ["⁰", "¹", "²", "³", "⁴", "⁶³", "⁶⁴", "⁶⁵", "¹²⁷"];
1424 for (test, expected) in TESTERS.iter().zip(OUTCOMES) {
1425 let actual = pwr2_to_exponent(*test);
1426 assert_eq!(
1427 actual, expected,
1428 "pwr2_to_exponent({test}) returned {actual}, expected {expected}",
1429 );
1430 }
1431 }
1432}