2 use hir::{known, Callable, HasVisibility, HirDisplay, Mutability, Semantics, TypeInfo};
4 base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
7 use itertools::Itertools;
8 use stdx::to_lower_snake_case;
10 ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
11 match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
17 #[derive(Clone, Debug, PartialEq, Eq)]
18 pub struct InlayHintsConfig {
19 pub render_colons: bool,
21 pub parameter_hints: bool,
22 pub chaining_hints: bool,
23 pub reborrow_hints: ReborrowHints,
24 pub closure_return_type_hints: ClosureReturnTypeHints,
25 pub binding_mode_hints: bool,
26 pub lifetime_elision_hints: LifetimeElisionHints,
27 pub param_names_for_lifetime_elision_hints: bool,
28 pub hide_named_constructor_hints: bool,
29 pub hide_closure_initialization_hints: bool,
30 pub max_length: Option<usize>,
31 pub closing_brace_hints_min_lines: Option<usize>,
34 #[derive(Clone, Debug, PartialEq, Eq)]
35 pub enum ClosureReturnTypeHints {
41 #[derive(Clone, Debug, PartialEq, Eq)]
42 pub enum LifetimeElisionHints {
48 #[derive(Clone, Debug, PartialEq, Eq)]
49 pub enum ReborrowHints {
55 #[derive(Clone, Debug, PartialEq, Eq)]
60 ClosureReturnTypeHint,
69 pub struct InlayHint {
73 pub tooltip: Option<InlayTooltip>,
77 pub enum InlayTooltip {
79 HoverRanged(FileId, TextRange),
80 HoverOffset(FileId, TextSize),
83 // Feature: Inlay Hints
85 // rust-analyzer shows additional information inline with the source code.
86 // Editors usually render this using read-only virtual text snippets interspersed with code.
88 // rust-analyzer by default shows hints for
90 // * types of local variables
91 // * names of function arguments
92 // * types of chained expressions
94 // Optionally, one can enable additional hints for
96 // * return types of closure expressions
98 // * compiler inserted reborrows
101 // | Editor | Action Name
103 // | VS Code | **Rust Analyzer: Toggle inlay hints*
106 // image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
107 pub(crate) fn inlay_hints(
110 range_limit: Option<FileRange>,
111 config: &InlayHintsConfig,
112 ) -> Vec<InlayHint> {
113 let _p = profile::span("inlay_hints");
114 let sema = Semantics::new(db);
115 let file = sema.parse(file_id);
116 let file = file.syntax();
118 let mut acc = Vec::new();
120 if let Some(scope) = sema.scope(&file) {
121 let famous_defs = FamousDefs(&sema, scope.krate());
123 let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
125 Some(FileRange { range, .. }) => match file.covering_element(range) {
126 NodeOrToken::Token(_) => return acc,
127 NodeOrToken::Node(n) => n
129 .filter(|descendant| range.intersect(descendant.text_range()).is_some())
132 None => file.descendants().for_each(hints),
140 hints: &mut Vec<InlayHint>,
141 famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
142 config: &InlayHintsConfig,
146 closing_brace_hints(hints, sema, config, file_id, node.clone());
150 chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
152 ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
153 ast::Expr::MethodCallExpr(it) => {
154 param_name_hints(hints, sema, config, ast::Expr::from(it))
156 ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it),
157 // We could show reborrows for all expressions, but usually that is just noise to the user
158 // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
159 ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
164 binding_mode_hints(hints, sema, config, &it);
165 if let ast::Pat::IdentPat(it) = it {
166 bind_pat_hints(hints, sema, config, file_id, &it);
170 ast::Item(it) => match it {
171 // FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
172 ast::Item::Impl(_) => None,
173 ast::Item::Fn(it) => fn_lifetime_fn_hints(hints, config, it),
174 // static type elisions
175 ast::Item::Static(it) => implicit_static_hints(hints, config, Either::Left(it)),
176 ast::Item::Const(it) => implicit_static_hints(hints, config, Either::Right(it)),
179 // FIXME: fn-ptr type, dyn fn type, and trait object type elisions
180 ast::Type(_) => None,
186 fn closing_brace_hints(
187 acc: &mut Vec<InlayHint>,
188 sema: &Semantics<'_, RootDatabase>,
189 config: &InlayHintsConfig,
193 let min_lines = config.closing_brace_hints_min_lines?;
195 let name = |it: ast::Name| it.syntax().text_range().start();
197 let mut closing_token;
198 let (label, name_offset) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
199 closing_token = item_list.r_curly_token()?;
201 let parent = item_list.syntax().parent()?;
205 let imp = sema.to_def(&imp)?;
206 let ty = imp.self_ty(sema.db);
207 let trait_ = imp.trait_(sema.db);
210 Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
211 None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
215 (format!("trait {}", tr.name()?), tr.name().map(name))
220 } else if let Some(list) = ast::ItemList::cast(node.clone()) {
221 closing_token = list.r_curly_token()?;
223 let module = ast::Module::cast(list.syntax().parent()?)?;
224 (format!("mod {}", module.name()?), module.name().map(name))
225 } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
226 closing_token = block.stmt_list()?.r_curly_token()?;
228 let parent = block.syntax().parent()?;
232 // FIXME: this could include parameters, but `HirDisplay` prints too much info
233 // and doesn't respect the max length either, so the hints end up way too long
234 (format!("fn {}", it.name()?), it.name().map(name))
236 ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
238 if it.underscore_token().is_some() {
239 ("const _".into(), None)
241 (format!("const {}", it.name()?), it.name().map(name))
247 } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
248 let last_token = mac.syntax().last_token()?;
249 if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
252 closing_token = last_token;
255 format!("{}!", mac.path()?),
256 mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range().start()),
262 if let Some(mut next) = closing_token.next_token() {
263 if next.kind() == T![;] {
264 if let Some(tok) = next.next_token() {
265 closing_token = next;
269 if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
270 // Only display the hint if the `}` is the last token on the line
276 node.text().for_each_chunk(|s| lines += s.matches('\n').count());
277 if lines < min_lines {
282 range: closing_token.text_range(),
283 kind: InlayKind::ClosingBraceHint,
285 tooltip: name_offset.map(|it| InlayTooltip::HoverOffset(file_id, it)),
291 fn implicit_static_hints(
292 acc: &mut Vec<InlayHint>,
293 config: &InlayHintsConfig,
294 statik_or_const: Either<ast::Static, ast::Const>,
296 if config.lifetime_elision_hints != LifetimeElisionHints::Always {
300 if let Either::Right(it) = &statik_or_const {
301 if ast::AssocItemList::can_cast(
302 it.syntax().parent().map_or(SyntaxKind::EOF, |it| it.kind()),
308 if let Some(ast::Type::RefType(ty)) = statik_or_const.either(|it| it.ty(), |it| it.ty()) {
309 if ty.lifetime().is_none() {
310 let t = ty.amp_token()?;
312 range: t.text_range(),
313 kind: InlayKind::LifetimeHint,
314 label: "'static".to_owned(),
315 tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
323 fn fn_lifetime_fn_hints(
324 acc: &mut Vec<InlayHint>,
325 config: &InlayHintsConfig,
328 if config.lifetime_elision_hints == LifetimeElisionHints::Never {
332 let mk_lt_hint = |t: SyntaxToken, label| InlayHint {
333 range: t.text_range(),
334 kind: InlayKind::LifetimeHint,
336 tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
339 let param_list = func.param_list()?;
340 let generic_param_list = func.generic_param_list();
341 let ret_type = func.ret_type();
342 let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
344 let is_elided = |lt: &Option<ast::Lifetime>| match lt {
345 Some(lt) => matches!(lt.text().as_str(), "'_"),
349 let potential_lt_refs = {
350 let mut acc: Vec<_> = vec![];
351 if let Some(self_param) = &self_param {
352 let lifetime = self_param.lifetime();
353 let is_elided = is_elided(&lifetime);
354 acc.push((None, self_param.amp_token(), lifetime, is_elided));
356 param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
357 // FIXME: check path types
358 walk_ty(&ty, &mut |ty| match ty {
359 ast::Type::RefType(r) => {
360 let lifetime = r.lifetime();
361 let is_elided = is_elided(&lifetime);
363 pat.as_ref().and_then(|it| match it {
364 ast::Pat::IdentPat(p) => p.name(),
379 let mut gen_idx_name = {
380 let mut gen = (0u8..).map(|idx| match idx {
381 idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
382 idx => format!("'{idx}").into(),
384 move || gen.next().unwrap_or_default()
386 let mut allocated_lifetimes = vec![];
388 let mut used_names: FxHashMap<SmolStr, usize> =
389 match config.param_names_for_lifetime_elision_hints {
390 true => generic_param_list
392 .flat_map(|gpl| gpl.lifetime_params())
393 .filter_map(|param| param.lifetime())
394 .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
396 false => Default::default(),
399 let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
400 if let Some(_) = &self_param {
401 if let Some(_) = potential_lt_refs.next() {
402 allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
403 // self can't be used as a lifetime, so no need to check for collisions
410 potential_lt_refs.for_each(|(name, ..)| {
411 let name = match name {
412 Some(it) if config.param_names_for_lifetime_elision_hints => {
413 if let Some(c) = used_names.get_mut(it.text().as_str()) {
415 SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
417 used_names.insert(it.text().as_str().into(), 0);
418 SmolStr::from_iter(["\'", it.text().as_str()])
423 allocated_lifetimes.push(name);
427 // fetch output lifetime if elision rule applies
428 let output = match potential_lt_refs.as_slice() {
429 [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
431 Some(lt) => match lt.text().as_str() {
432 "'_" => allocated_lifetimes.get(0).cloned(),
434 name => Some(name.into()),
436 None => allocated_lifetimes.get(0).cloned(),
442 if allocated_lifetimes.is_empty() && output.is_none() {
447 // apply output if required
448 let mut is_trivial = true;
449 if let (Some(output_lt), Some(r)) = (&output, ret_type) {
450 if let Some(ty) = r.ty() {
451 walk_ty(&ty, &mut |ty| match ty {
452 ast::Type::RefType(ty) if ty.lifetime().is_none() => {
453 if let Some(amp) = ty.amp_token() {
455 acc.push(mk_lt_hint(amp, output_lt.to_string()));
463 if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
467 let mut a = allocated_lifetimes.iter();
468 for (_, amp_token, _, is_elided) in potential_lt_refs {
472 acc.push(mk_lt_hint(t, lt.to_string()));
476 // generate generic param list things
477 match (generic_param_list, allocated_lifetimes.as_slice()) {
479 (Some(gpl), allocated_lifetimes) => {
480 let angle_tok = gpl.l_angle_token()?;
481 let is_empty = gpl.generic_params().next().is_none();
483 range: angle_tok.text_range(),
484 kind: InlayKind::LifetimeHint,
487 allocated_lifetimes.iter().format(", "),
488 if is_empty { "" } else { ", " }
490 tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
493 (None, allocated_lifetimes) => acc.push(InlayHint {
494 range: func.name()?.syntax().text_range(),
495 kind: InlayKind::GenericParamListHint,
496 label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
497 tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
503 fn closure_ret_hints(
504 acc: &mut Vec<InlayHint>,
505 sema: &Semantics<'_, RootDatabase>,
506 famous_defs: &FamousDefs<'_, '_>,
507 config: &InlayHintsConfig,
509 closure: ast::ClosureExpr,
511 if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
515 if closure.ret_type().is_some() {
519 if !closure_has_block_body(&closure)
520 && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
525 let param_list = closure.param_list()?;
527 let closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
528 let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
529 let callable = ty.as_callable(sema.db)?;
530 let ty = callable.return_type();
535 range: param_list.syntax().text_range(),
536 kind: InlayKind::ClosureReturnTypeHint,
537 label: hint_iterator(sema, &famous_defs, config, &ty)
538 .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()),
539 tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
545 acc: &mut Vec<InlayHint>,
546 sema: &Semantics<'_, RootDatabase>,
547 config: &InlayHintsConfig,
550 if config.reborrow_hints == ReborrowHints::Never {
554 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
555 let desc_expr = descended.as_ref().unwrap_or(expr);
556 let mutability = sema.is_implicit_reborrow(desc_expr)?;
557 let label = match mutability {
558 hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
559 hir::Mutability::Mut => "&mut *",
563 range: expr.syntax().text_range(),
564 kind: InlayKind::ImplicitReborrowHint,
565 label: label.to_string(),
566 tooltip: Some(InlayTooltip::String("Compiler inserted reborrow".into())),
572 acc: &mut Vec<InlayHint>,
573 sema: &Semantics<'_, RootDatabase>,
574 famous_defs: &FamousDefs<'_, '_>,
575 config: &InlayHintsConfig,
579 if !config.chaining_hints {
583 if matches!(expr, ast::Expr::RecordExpr(_)) {
587 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
588 let desc_expr = descended.as_ref().unwrap_or(expr);
590 let mut tokens = expr
592 .siblings_with_tokens(Direction::Next)
593 .filter_map(NodeOrToken::into_token)
594 .filter(|t| match t.kind() {
595 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
596 SyntaxKind::COMMENT => false,
600 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
601 // Ignoring extra whitespace and comments
602 let next = tokens.next()?.kind();
603 if next == SyntaxKind::WHITESPACE {
604 let mut next_next = tokens.next()?.kind();
605 while next_next == SyntaxKind::WHITESPACE {
606 next_next = tokens.next()?.kind();
608 if next_next == T![.] {
609 let ty = sema.type_of_expr(desc_expr)?.original;
613 if matches!(expr, ast::Expr::PathExpr(_)) {
614 if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
615 if st.fields(sema.db).is_empty() {
621 range: expr.syntax().text_range(),
622 kind: InlayKind::ChainingHint,
623 label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| {
624 ty.display_truncated(sema.db, config.max_length).to_string()
626 tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
634 acc: &mut Vec<InlayHint>,
635 sema: &Semantics<'_, RootDatabase>,
636 config: &InlayHintsConfig,
639 if !config.parameter_hints {
643 let (callable, arg_list) = get_callable(sema, &expr)?;
647 .zip(arg_list.args())
648 .filter_map(|((param, _ty), arg)| {
649 // Only annotate hints for expressions that exist in the original file
650 let range = sema.original_range_opt(arg.syntax())?;
651 let (param_name, name_syntax) = match param.as_ref()? {
652 Either::Left(pat) => ("self".to_string(), pat.name()),
653 Either::Right(pat) => match pat {
654 ast::Pat::IdentPat(it) => (it.name()?.to_string(), it.name()),
658 Some((name_syntax, param_name, arg, range))
660 .filter(|(_, param_name, arg, _)| {
661 !should_hide_param_name_hint(sema, &callable, param_name, arg)
663 .map(|(param, param_name, _, FileRange { range, .. })| {
664 let mut tooltip = None;
665 if let Some(name) = param {
666 if let hir::CallableKind::Function(f) = callable.kind() {
667 // assert the file is cached so we can map out of macros
668 if let Some(_) = sema.source(f) {
669 tooltip = sema.original_range_opt(name.syntax());
676 kind: InlayKind::ParameterHint,
678 tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())),
686 fn binding_mode_hints(
687 acc: &mut Vec<InlayHint>,
688 sema: &Semantics<'_, RootDatabase>,
689 config: &InlayHintsConfig,
692 if !config.binding_mode_hints {
696 let range = pat.syntax().text_range();
697 sema.pattern_adjustments(&pat).iter().for_each(|ty| {
698 let reference = ty.is_reference();
699 let mut_reference = ty.is_mutable_reference();
700 let r = match (reference, mut_reference) {
701 (true, true) => "&mut",
702 (true, false) => "&",
707 kind: InlayKind::BindingModeHint,
708 label: r.to_string(),
709 tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
713 ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
714 let bm = sema.binding_mode_of_pat(pat)?;
716 hir::BindingMode::Move => return None,
717 hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
718 hir::BindingMode::Ref(Mutability::Shared) => "ref",
722 kind: InlayKind::BindingModeHint,
723 label: bm.to_string(),
724 tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
734 acc: &mut Vec<InlayHint>,
735 sema: &Semantics<'_, RootDatabase>,
736 config: &InlayHintsConfig,
740 if !config.type_hints {
744 let descended = sema.descend_node_into_attributes(pat.clone()).pop();
745 let desc_pat = descended.as_ref().unwrap_or(pat);
746 let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
748 if should_not_display_type_hint(sema, config, pat, &ty) {
752 let krate = sema.scope(desc_pat.syntax())?.krate();
753 let famous_defs = FamousDefs(sema, krate);
754 let label = hint_iterator(sema, &famous_defs, config, &ty);
756 let label = match label {
757 Some(label) => label,
759 let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
760 if config.hide_named_constructor_hints
761 && is_named_constructor(sema, pat, &ty_name).is_some()
770 range: match pat.name() {
771 Some(name) => name.syntax().text_range(),
772 None => pat.syntax().text_range(),
774 kind: InlayKind::TypeHint,
778 .map(|it| it.syntax().text_range())
779 .map(|it| InlayTooltip::HoverRanged(file_id, it)),
785 fn is_named_constructor(
786 sema: &Semantics<'_, RootDatabase>,
790 let let_node = pat.syntax().parent()?;
791 let expr = match_ast! {
793 ast::LetStmt(it) => it.initializer(),
794 ast::LetExpr(it) => it.expr(),
799 let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
800 // unwrap postfix expressions
801 let expr = match expr {
802 ast::Expr::TryExpr(it) => it.expr(),
803 ast::Expr::AwaitExpr(it) => it.expr(),
806 let expr = match expr {
807 ast::Expr::CallExpr(call) => match call.expr()? {
808 ast::Expr::PathExpr(path) => path,
811 ast::Expr::PathExpr(path) => path,
814 let path = expr.path()?;
816 let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
817 let callable_kind = callable.map(|it| it.kind());
818 let qual_seg = match callable_kind {
819 Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
820 path.qualifier()?.segment()
825 let ctor_name = match qual_seg.kind()? {
826 ast::PathSegmentKind::Name(name_ref) => {
827 match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
828 Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
829 None => name_ref.to_string(),
832 ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
835 (ctor_name == ty_name).then(|| ())
838 /// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
840 sema: &Semantics<'_, RootDatabase>,
841 famous_defs: &FamousDefs<'_, '_>,
842 config: &InlayHintsConfig,
844 ) -> Option<String> {
846 let strukt = ty.strip_references().as_adt()?;
847 let krate = strukt.module(db).krate();
848 if krate != famous_defs.core()? {
851 let iter_trait = famous_defs.core_iter_Iterator()?;
852 let iter_mod = famous_defs.core_iter()?;
854 // Assert that this struct comes from `core::iter`.
855 if !(strukt.visibility(db) == hir::Visibility::Public
856 && strukt.module(db).path_to_root(db).contains(&iter_mod))
861 if ty.impls_trait(db, iter_trait, &[]) {
862 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
863 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
866 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
867 const LABEL_START: &str = "impl Iterator<Item = ";
868 const LABEL_END: &str = ">";
870 let ty_display = hint_iterator(sema, famous_defs, config, &ty)
871 .map(|assoc_type_impl| assoc_type_impl.to_string())
873 ty.display_truncated(
877 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
881 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
888 fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
889 if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
890 let pat_text = bind_pat.to_string();
894 .map(|variant| variant.name(db).to_smol_str())
895 .any(|enum_name| enum_name == pat_text)
901 fn should_not_display_type_hint(
902 sema: &Semantics<'_, RootDatabase>,
903 config: &InlayHintsConfig,
904 bind_pat: &ast::IdentPat,
909 if pat_ty.is_unknown() {
913 if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
914 if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
919 if config.hide_closure_initialization_hints {
920 if let Some(parent) = bind_pat.syntax().parent() {
921 if let Some(it) = ast::LetStmt::cast(parent.clone()) {
922 if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
923 if closure_has_block_body(&closure) {
931 for node in bind_pat.syntax().ancestors() {
934 ast::LetStmt(it) => return it.ty().is_some(),
935 // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
936 ast::Param(it) => return it.ty().is_some(),
937 ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
938 ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
939 ast::IfExpr(_) => return false,
940 ast::WhileExpr(_) => return false,
941 ast::ForExpr(it) => {
942 // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
943 // Type of expr should be iterable.
944 return it.in_token().is_none() ||
946 .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
947 .map(TypeInfo::original)
948 .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
957 fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
958 matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
961 fn should_hide_param_name_hint(
962 sema: &Semantics<'_, RootDatabase>,
963 callable: &hir::Callable,
965 argument: &ast::Expr,
967 // These are to be tested in the `parameter_hint_heuristics` test
969 // - the parameter name is a suffix of the function's name
970 // - the argument is a qualified constructing or call expression where the qualifier is an ADT
971 // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
972 // of argument with _ splitting it off
973 // - param starts with `ra_fixture`
974 // - param is a well known name in a unary function
976 let param_name = param_name.trim_start_matches('_');
977 if param_name.is_empty() {
981 if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
985 let fn_name = match callable.kind() {
986 hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
989 let fn_name = fn_name.as_deref();
990 is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
991 || is_argument_similar_to_param_name(argument, param_name)
992 || param_name.starts_with("ra_fixture")
993 || (callable.n_params() == 1 && is_obvious_param(param_name))
994 || is_adt_constructor_similar_to_param_name(sema, argument, param_name)
997 fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
998 // check whether param_name and argument are the same or
999 // whether param_name is a prefix/suffix of argument(split at `_`)
1000 let argument = match get_string_representation(argument) {
1001 Some(argument) => argument,
1002 None => return false,
1005 // std is honestly too panic happy...
1006 let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
1008 let param_name = param_name.trim_start_matches('_');
1009 let argument = argument.trim_start_matches('_');
1011 match str_split_at(argument, param_name.len()) {
1012 Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
1013 return rest.is_empty() || rest.starts_with('_');
1017 match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
1018 Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
1019 return rest.is_empty() || rest.ends_with('_');
1026 /// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
1028 /// `fn strip_suffix(suffix)` will be hidden.
1029 /// `fn stripsuffix(suffix)` will not be hidden.
1030 fn is_param_name_suffix_of_fn_name(
1032 callable: &Callable,
1033 fn_name: Option<&str>,
1035 match (callable.n_params(), fn_name) {
1036 (1, Some(function)) => {
1037 function == param_name
1040 .checked_sub(param_name.len())
1041 .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
1042 .map_or(false, |(prefix, suffix)| {
1043 suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
1050 fn is_adt_constructor_similar_to_param_name(
1051 sema: &Semantics<'_, RootDatabase>,
1052 argument: &ast::Expr,
1055 let path = match argument {
1056 ast::Expr::CallExpr(c) => c.expr().and_then(|e| match e {
1057 ast::Expr::PathExpr(p) => p.path(),
1060 ast::Expr::PathExpr(p) => p.path(),
1061 ast::Expr::RecordExpr(r) => r.path(),
1064 let path = match path {
1066 None => return false,
1068 (|| match sema.resolve_path(&path)? {
1069 hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
1070 Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
1072 hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_)) => {
1073 if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
1076 let qual = path.qualifier()?;
1077 match sema.resolve_path(&qual)? {
1078 hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
1079 Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
1089 fn get_string_representation(expr: &ast::Expr) -> Option<String> {
1091 ast::Expr::MethodCallExpr(method_call_expr) => {
1092 let name_ref = method_call_expr.name_ref()?;
1093 match name_ref.text().as_str() {
1094 "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
1095 name_ref => Some(name_ref.to_owned()),
1098 ast::Expr::MacroExpr(macro_expr) => {
1099 Some(macro_expr.macro_call()?.path()?.segment()?.to_string())
1101 ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
1102 ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
1103 ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
1104 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
1105 ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
1110 fn is_obvious_param(param_name: &str) -> bool {
1111 // avoid displaying hints for common functions like map, filter, etc.
1112 // or other obvious words used in std
1113 let is_obvious_param_name =
1114 matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
1115 param_name.len() == 1 || is_obvious_param_name
1119 sema: &Semantics<'_, RootDatabase>,
1121 ) -> Option<(hir::Callable, ast::ArgList)> {
1123 ast::Expr::CallExpr(expr) => {
1124 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
1125 let expr = descended.as_ref().unwrap_or(expr);
1126 sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
1128 ast::Expr::MethodCallExpr(expr) => {
1129 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
1130 let expr = descended.as_ref().unwrap_or(expr);
1131 sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
1139 use expect_test::{expect, Expect};
1140 use ide_db::base_db::FileRange;
1141 use itertools::Itertools;
1142 use syntax::{TextRange, TextSize};
1143 use test_utils::extract_annotations;
1145 use crate::inlay_hints::ReborrowHints;
1146 use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
1148 use super::ClosureReturnTypeHints;
1150 const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
1151 render_colons: false,
1153 parameter_hints: false,
1154 chaining_hints: false,
1155 lifetime_elision_hints: LifetimeElisionHints::Never,
1156 closure_return_type_hints: ClosureReturnTypeHints::Never,
1157 reborrow_hints: ReborrowHints::Always,
1158 binding_mode_hints: false,
1159 hide_named_constructor_hints: false,
1160 hide_closure_initialization_hints: false,
1161 param_names_for_lifetime_elision_hints: false,
1163 closing_brace_hints_min_lines: None,
1165 const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
1167 parameter_hints: true,
1168 chaining_hints: true,
1169 reborrow_hints: ReborrowHints::Always,
1170 closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
1171 binding_mode_hints: true,
1172 lifetime_elision_hints: LifetimeElisionHints::Always,
1177 fn check(ra_fixture: &str) {
1178 check_with_config(TEST_CONFIG, ra_fixture);
1182 fn check_params(ra_fixture: &str) {
1184 InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
1190 fn check_types(ra_fixture: &str) {
1191 check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
1195 fn check_chains(ra_fixture: &str) {
1196 check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
1200 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
1201 let (analysis, file_id) = fixture::file(ra_fixture);
1202 let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
1203 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1204 let actual = inlay_hints
1206 .map(|it| (it.range, it.label.to_string()))
1207 .sorted_by_key(|(range, _)| range.start())
1208 .collect::<Vec<_>>();
1209 expected.sort_by_key(|(range, _)| range.start());
1211 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
1215 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
1216 let (analysis, file_id) = fixture::file(ra_fixture);
1217 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1218 expect.assert_debug_eq(&inlay_hints)
1222 fn hints_disabled() {
1224 InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
1226 fn foo(a: i32, b: i32) -> i32 { a + b }
1233 // Parameter hint tests
1236 fn param_hints_only() {
1239 fn foo(a: i32, b: i32) -> i32 { a + b }
1252 fn param_hints_on_closure() {
1256 let clo = |a: u8, b: u8| a + b;
1269 fn param_name_similar_to_fn_name_still_hints() {
1272 fn max(x: i32, y: i32) -> i32 { x + y }
1285 fn param_name_similar_to_fn_name() {
1288 fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
1290 let _x = param_with_underscore(
1297 fn param_with_underscore(underscore: i32) -> i32 { underscore }
1299 let _x = param_with_underscore(
1307 fn param_name_same_as_fn_name() {
1310 fn foo(foo: i32) -> i32 { foo }
1320 fn never_hide_param_when_multiple_params() {
1323 fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
1336 fn param_hints_look_through_as_ref_and_clone() {
1339 fn foo(bar: i32, baz: f32) {}
1345 foo(bar.clone(), bar.clone());
1347 foo(bar.as_ref(), bar.as_ref());
1355 fn self_param_hints() {
1361 fn foo(self: Self) {}
1362 fn bar(self: &Self) {}
1376 fn param_name_hints_show_for_literals() {
1378 r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
1391 fn function_call_parameter_hint() {
1394 //- minicore: option
1399 struct SyntaxKind {}
1400 struct NavigationTarget {}
1405 fn method(&self, mut param: i32) -> i32 { param * 2 }
1410 focus_range: Option<TextRange>,
1411 full_range: TextRange,
1413 docs: Option<String>,
1414 ) -> NavigationTarget {
1419 fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
1424 let not_literal = 1;
1425 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
1426 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
1427 let t: Test = Test {};
1430 Test::method(&t, 3456);
1431 //^^ self ^^^^ param
1435 //^^^^^^^^^^^^^ name
1439 //^^^^^^^^^^^^ full_range
1441 //^^^^^^^^^^^^^ kind
1450 fn parameter_hint_heuristics() {
1453 fn check(ra_fixture_thing: &str) {}
1456 fn filter(predicate: i32) {}
1458 fn strip_suffix(suffix: &str) {}
1459 fn stripsuffix(suffix: &str) {}
1460 fn same(same: u32) {}
1461 fn same2(_same2: u32) {}
1463 fn enum_matches_param_name(completion_kind: CompletionKind) {}
1465 fn foo(param: u32) {}
1466 fn bar(param_eter: u32) {}
1468 enum CompletionKind {
1472 fn non_ident_pat((a, b): (u32, u32)) {}
1475 const PARAM: u32 = 0;
1490 enum_matches_param_name(CompletionKind::Keyword);
1497 let start_param = 0;
1503 macro_rules! param {
1510 let param_eter_end = 0;
1511 bar(param_eter_end);
1512 let start_param_eter = 0;
1513 bar(start_param_eter);
1514 let param_eter2 = 0;
1516 //^^^^^^^^^^^ param_eter
1518 non_ident_pat((0, 0));
1526 fn type_hints_only() {
1529 fn foo(a: i32, b: i32) -> i32 { a + b }
1538 fn type_hints_bindings_after_at() {
1541 //- minicore: option
1543 let ref foo @ bar @ ref mut baz = 0;
1549 if let x @ Some(_) = Some(0) {}
1551 let foo @ (bar, baz) = (3, 3);
1560 fn default_generic_types_should_not_be_displayed() {
1563 struct Test<K, T = u8> { k: K, t: T }
1566 let zz = Test { t: 23u8, k: 33 };
1571 //^^^^ || -> Test<i32>
1577 fn shorten_iterators_in_associated_params() {
1580 //- minicore: iterators
1583 pub struct SomeIter<T> {}
1585 impl<T> SomeIter<T> {
1586 pub fn new() -> Self { SomeIter {} }
1587 pub fn push(&mut self, t: T) {}
1590 impl<T> Iterator for SomeIter<T> {
1592 fn next(&mut self) -> Option<Self::Item> {
1598 let mut some_iter = SomeIter::new();
1599 //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
1600 some_iter.push(iter::repeat(2).take(2));
1601 let iter_of_iters = some_iter.take(2);
1602 //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
1609 fn infer_call_method_return_associated_types_with_generic() {
1613 fn default() -> Self;
1619 pub fn quux<T: Foo>() -> T::Bar {
1620 let y = Default::default();
1633 //- minicore: fn, sized
1634 fn foo() -> impl Fn() { loop {} }
1635 fn foo1() -> impl Fn(f64) { loop {} }
1636 fn foo2() -> impl Fn(f64, f64) { loop {} }
1637 fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
1638 fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
1639 fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
1640 fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
1641 fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
1649 // ^^^ impl Fn(f64, f64)
1651 // ^^^ impl Fn(f64, f64) -> u32
1653 // ^^^ &dyn Fn(f64, f64) -> u32
1655 // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
1657 // ^^^ impl Fn(f64, f64) -> u32
1659 // ^^^ *const impl Fn(f64, f64) -> u32
1666 fn check_hint_range_limit() {
1668 //- minicore: fn, sized
1669 fn foo() -> impl Fn() { loop {} }
1670 fn foo1() -> impl Fn(f64) { loop {} }
1671 fn foo2() -> impl Fn(f64, f64) { loop {} }
1672 fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
1673 fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
1674 fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
1675 fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
1676 fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
1682 // ^^^ impl Fn(f64, f64)
1684 // ^^^ impl Fn(f64, f64) -> u32
1691 let (analysis, file_id) = fixture::file(fixture);
1692 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
1693 let inlay_hints = analysis
1695 &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
1699 range: TextRange::new(TextSize::from(500), TextSize::from(600)),
1704 inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
1705 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
1709 fn fn_hints_ptr_rpit_fn_parentheses() {
1712 //- minicore: fn, sized
1715 fn foo1() -> *const impl Fn() { loop {} }
1716 fn foo2() -> *const (impl Fn() + Sized) { loop {} }
1717 fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
1718 fn foo4() -> *const (impl Sized + Fn()) { loop {} }
1719 fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
1720 fn foo6() -> *const (impl Fn() + Trait) { loop {} }
1721 fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
1722 fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
1723 fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
1724 fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
1728 // ^^^ *const impl Fn()
1730 // ^^^ *const impl Fn()
1732 // ^^^ *const (impl Fn() + ?Sized)
1734 // ^^^ *const impl Fn()
1736 // ^^^ *const (impl Fn() + ?Sized)
1738 // ^^^ *const (impl Fn() + Trait)
1740 // ^^^ *const (impl Fn() + Trait)
1742 // ^^^ *const (impl Fn() + Trait + ?Sized)
1744 // ^^^ *const (impl Fn() -> u8 + ?Sized)
1746 // ^^^ *const impl Fn()
1753 fn unit_structs_have_no_type_hints() {
1756 //- minicore: result
1757 struct SyntheticSyntax;
1762 Err(SyntheticSyntax) => (),
1769 fn let_statement() {
1772 #[derive(PartialEq)]
1773 enum Option<T> { None, Some(T) }
1775 #[derive(PartialEq)]
1776 struct Test { a: Option<u32>, b: u8 }
1779 struct InnerStruct {}
1789 let test = InnerStruct {};
1792 let test = unresolved();
1794 let test = (42, 'a');
1796 let (a, (b, (c,)) = (2, (3, (9.2,));
1808 //- minicore: option
1809 struct Test { a: Option<u32>, b: u8 }
1812 let test = Some(Test { a: Some(3), b: 1 });
1814 if let None = &test {};
1815 if let test = &test {};
1816 //^^^^ &Option<Test>
1817 if let Some(test) = &test {};
1819 if let Some(Test { a, b }) = &test {};
1820 //^ &Option<u32> ^ &u8
1821 if let Some(Test { a: x, b: y }) = &test {};
1822 //^ &Option<u32> ^ &u8
1823 if let Some(Test { a: Some(x), b: y }) = &test {};
1825 if let Some(Test { a: None, b: y }) = &test {};
1827 if let Some(Test { b: y, .. }) = &test {};
1838 //- minicore: option
1839 struct Test { a: Option<u32>, b: u8 }
1842 let test = Some(Test { a: Some(3), b: 1 });
1844 while let Some(Test { a: Some(x), b: y }) = &test {};
1851 fn match_arm_list() {
1854 //- minicore: option
1855 struct Test { a: Option<u32>, b: u8 }
1858 match Some(Test { a: Some(3), b: 1 }) {
1862 Some(Test { a: Some(x), b: y }) => (),
1871 fn complete_for_hint() {
1874 //- minicore: iterator
1875 pub struct Vec<T> {}
1878 pub fn new() -> Self { Vec {} }
1879 pub fn push(&mut self, t: T) {}
1882 impl<T> IntoIterator for Vec<T> {
1887 let mut data = Vec::new();
1901 fn multi_dyn_trait_bounds() {
1904 pub struct Vec<T> {}
1907 pub fn new() -> Self { Vec {} }
1910 pub struct Box<T> {}
1916 // The block expression wrapping disables the constructor hint hiding logic
1917 let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
1918 //^^ Vec<Box<&(dyn Display + Sync)>>
1919 let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
1920 //^^ Vec<Box<*const (dyn Display + Sync)>>
1921 let _v = { Vec::<Box<dyn Display + Sync>>::new() };
1922 //^^ Vec<Box<dyn Display + Sync>>
1929 fn shorten_iterator_hints() {
1932 //- minicore: iterators
1937 impl Iterator for MyIter {
1939 fn next(&mut self) -> Option<Self::Item> {
1947 let _x = iter::repeat(0);
1948 //^^ impl Iterator<Item = i32>
1949 fn generic<T: Clone>(t: T) {
1950 let _x = iter::repeat(t);
1951 //^^ impl Iterator<Item = T>
1952 let _chained = iter::repeat(t).take(10);
1953 //^^^^^^^^ impl Iterator<Item = T>
1961 fn skip_constructor_and_enum_type_hints() {
1965 hide_named_constructor_hints: true,
1969 //- minicore: try, option
1970 use core::ops::ControlFlow;
1973 pub mod y { pub struct Foo; }
1975 pub enum AnotherEnum {
1980 struct TupleStruct();
1986 fn try_new() -> ControlFlow<(), Self> {
1987 ControlFlow::Continue(Struct)
1991 struct Generic<T>(T);
2002 fn times2(value: i32) -> i32 {
2007 let enumb = Enum::Variant(0);
2009 let strukt = x::Foo;
2010 let strukt = x::y::Foo;
2011 let strukt = Struct;
2012 let strukt = Struct::new();
2014 let tuple_struct = TupleStruct();
2016 let generic0 = Generic::new();
2017 // ^^^^^^^^ Generic<i32>
2018 let generic1 = Generic(0);
2019 // ^^^^^^^^ Generic<i32>
2020 let generic2 = Generic::<i32>::new();
2021 let generic3 = <Generic<i32>>::new();
2022 let generic4 = Generic::<i32>(0);
2025 let option = Some(0);
2026 // ^^^^^^ Option<i32>
2028 // ^^^^ fn times2(i32) -> i32
2029 let closure = |x: i32| x * 2;
2030 // ^^^^^^^ |i32| -> i32
2033 fn fallible() -> ControlFlow<()> {
2034 let strukt = Struct::try_new()?;
2041 fn shows_constructor_type_hints_when_enabled() {
2045 use core::ops::ControlFlow;
2048 struct TupleStruct();
2054 fn try_new() -> ControlFlow<(), Self> {
2055 ControlFlow::Continue(Struct)
2059 struct Generic<T>(T);
2067 let strukt = Struct::new();
2069 let tuple_struct = TupleStruct();
2070 // ^^^^^^^^^^^^ TupleStruct
2071 let generic0 = Generic::new();
2072 // ^^^^^^^^ Generic<i32>
2073 let generic1 = Generic::<i32>::new();
2074 // ^^^^^^^^ Generic<i32>
2075 let generic2 = <Generic<i32>>::new();
2076 // ^^^^^^^^ Generic<i32>
2079 fn fallible() -> ControlFlow<()> {
2080 let strukt = Struct::try_new()?;
2094 (0..2).for_each(|increment | { start += increment; });
2098 //^^^^^^^^ |i32, i32| -> i32
2104 let _: i32 = multiply(1, 2);
2106 let multiply_ref = &multiply;
2107 //^^^^^^^^^^^^ &|i32, i32| -> i32
2109 let return_42 = || 42;
2110 //^^^^^^^^^ || -> i32
2118 fn return_type_hints_for_closure_without_block() {
2121 closure_return_type_hints: ClosureReturnTypeHints::Always,
2135 fn skip_closure_type_hints() {
2139 hide_closure_initialization_hints: true,
2145 let multiple_2 = |x: i32| { x * 2 };
2147 let multiple_2 = |x: i32| x * 2;
2148 // ^^^^^^^^^^ |i32| -> i32
2150 let (not) = (|x: bool| { !x });
2151 // ^^^ |bool| -> bool
2153 let (is_zero, _b) = (|x: usize| { x == 0 }, false);
2154 // ^^^^^^^ |usize| -> bool
2157 let plus_one = |x| { x + 1 };
2161 let add_mul = bar(|x: u8| { x + 1 });
2162 // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized
2164 let closure = if let Some(6) = add_mul(2).checked_sub(1) {
2165 // ^^^^^^^ fn(i32) -> i32
2172 fn foo(f: impl FnOnce(u8) -> u8) {}
2174 fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 {
2175 move |x: u8| f(x) * 2
2182 fn hint_truncation() {
2184 InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
2188 struct VeryLongOuterName<T>(T);
2193 let b = VeryLongOuterName(0usize);
2194 //^ VeryLongOuterName<…>
2195 let c = Smol(Smol(0u32))
2201 // Chaining hint tests
2204 fn chaining_hints_ignore_comments() {
2206 InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
2209 impl A { fn into_b(self) -> B { self.0 } }
2211 impl B { fn into_c(self) -> C { self.0 } }
2216 .into_b() // This is a comment
2217 // This is another comment
2255 fn chaining_hints_without_newlines() {
2259 impl A { fn into_b(self) -> B { self.0 } }
2261 impl B { fn into_c(self) -> C { self.0 } }
2265 let c = A(B(C)).into_b().into_c();
2271 fn struct_access_chaining_hints() {
2273 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2275 struct A { pub b: B }
2276 struct B { pub c: C }
2281 fn foo(&self) -> i32 { 42 }
2285 let x = A { b: B { c: C(true) } }
2326 fn generic_chaining_hints() {
2328 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2333 struct X<T,R>(T, R);
2336 fn new(t: T) -> Self { A(t) }
2337 fn into_b(self) -> B<T> { B(self.0) }
2340 fn into_c(self) -> C<T> { C(self.0) }
2343 let c = A::new(X(42, true))
2353 label: "B<X<i32, bool>>",
2366 label: "A<X<i32, bool>>",
2382 fn shorten_iterator_chaining_hints() {
2384 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2386 //- minicore: iterators
2391 impl Iterator for MyIter {
2393 fn next(&mut self) -> Option<Self::Item> {
2399 let _x = MyIter.by_ref()
2411 label: "impl Iterator<Item = ()>",
2424 label: "impl Iterator<Item = ()>",
2437 label: "impl Iterator<Item = ()>",
2450 label: "&mut MyIter",
2466 fn hints_in_attr_call() {
2470 //- proc_macros: identity, input_replace
2473 fn chain(self) -> Self {
2477 #[proc_macros::identity]
2479 let strukt = Struct;
2484 Struct::chain(strukt);
2530 kind: ParameterHint,
2547 fn hints_lifetimes() {
2552 fn no_gpl(a: &()) {}
2555 fn empty_gpl<>(a: &()) {}
2557 fn partial<'b>(a: &(), b: &'b ()) {}
2559 fn partial<'a>(a: &'a (), b: &()) {}
2562 fn single_ret(a: &()) -> &() {}
2565 fn full_mul(a: &(), b: &()) {}
2569 fn foo<'c>(a: &'c ()) -> &() {}
2572 fn nested_in(a: & &X< &()>) {}
2573 // ^^^^^^^^^<'0, '1, '2>
2575 fn nested_out(a: &()) -> & &X< &()>{}
2583 fn foo(&self) -> &() {}
2586 fn foo(&self, a: &()) -> &() {}
2595 fn hints_lifetimes_named() {
2597 InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
2599 fn nested_in<'named>(named: & &X< &()>) {}
2600 // ^'named1, 'named2, 'named3, $
2601 //^'named1 ^'named2 ^'named3
2607 fn hints_lifetimes_trivial_skip() {
2610 lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
2614 fn no_gpl(a: &()) {}
2615 fn empty_gpl<>(a: &()) {}
2616 fn partial<'b>(a: &(), b: &'b ()) {}
2617 fn partial<'a>(a: &'a (), b: &()) {}
2619 fn single_ret(a: &()) -> &() {}
2622 fn full_mul(a: &(), b: &()) {}
2624 fn foo<'c>(a: &'c ()) -> &() {}
2627 fn nested_in(a: & &X< &()>) {}
2628 fn nested_out(a: &()) -> & &X< &()>{}
2634 fn foo(&self) -> &() {}
2637 fn foo(&self, a: &()) -> &() {}
2646 fn hints_lifetimes_static() {
2649 lifetime_elision_hints: LifetimeElisionHints::Always,
2654 static S: &str = "";
2658 const C: &dyn Trait = panic!();
2663 const C: &dyn Trait = panic!();
2670 fn hints_implicit_reborrow() {
2673 reborrow_hints: ReborrowHints::Always,
2674 parameter_hints: true,
2679 let unique = &mut ();
2681 let foo: &mut _ = unique;
2686 let shared = ref_id(unique);
2690 let r_mov: &_ = shared;
2697 fn identity<T>(t: T) -> T {
2700 fn ref_mut_id(mut_ref: &mut ()) -> &mut () {
2704 fn ref_id(shared_ref: &()) -> &() {
2712 fn hints_binding_modes() {
2714 InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
2729 let (x,) = &mut (0,);
2732 let &mut (x,) = &mut (0,);
2733 let (ref mut x,) = &mut (0,);
2735 let &mut (ref mut x,) = &mut (0,);
2736 let (mut x,) = &mut (0,);
2756 fn hints_closing_brace() {
2758 InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
2763 } // no hint unless `}` is the last token on the line
2769 fn h<T>(with: T, arguments: u8, ...) {