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: bool,
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 max_length: Option<usize>,
30 pub closing_brace_hints_min_lines: Option<usize>,
33 #[derive(Clone, Debug, PartialEq, Eq)]
34 pub enum LifetimeElisionHints {
40 #[derive(Clone, Debug, PartialEq, Eq)]
41 pub enum ReborrowHints {
47 #[derive(Clone, Debug, PartialEq, Eq)]
51 ClosingBraceHint(Option<TextSize>),
52 ClosureReturnTypeHint,
61 pub struct InlayHint {
67 // Feature: Inlay Hints
69 // rust-analyzer shows additional information inline with the source code.
70 // Editors usually render this using read-only virtual text snippets interspersed with code.
72 // rust-analyzer by default shows hints for
74 // * types of local variables
75 // * names of function arguments
76 // * types of chained expressions
78 // Optionally, one can enable additional hints for
80 // * return types of closure expressions with blocks
82 // * compiler inserted reborrows
85 // | Editor | Action Name
87 // | VS Code | **Rust Analyzer: Toggle inlay hints*
90 // image::https://user-images.githubusercontent.com/48062697/113020660-b5f98b80-917a-11eb-8d70-3be3fd558cdd.png[]
91 pub(crate) fn inlay_hints(
94 range_limit: Option<FileRange>,
95 config: &InlayHintsConfig,
97 let _p = profile::span("inlay_hints");
98 let sema = Semantics::new(db);
99 let file = sema.parse(file_id);
100 let file = file.syntax();
102 let mut acc = Vec::new();
104 let hints = |node| hints(&mut acc, &sema, config, node);
106 Some(FileRange { range, .. }) => match file.covering_element(range) {
107 NodeOrToken::Token(_) => return acc,
108 NodeOrToken::Node(n) => n
110 .filter(|descendant| range.intersect(descendant.text_range()).is_some())
113 None => file.descendants().for_each(hints),
120 hints: &mut Vec<InlayHint>,
121 sema: &Semantics<RootDatabase>,
122 config: &InlayHintsConfig,
125 let famous_defs = match sema.scope(&node) {
126 Some(it) => FamousDefs(sema, it.krate()),
130 closing_brace_hints(hints, sema, config, node.clone());
134 chaining_hints(hints, sema, &famous_defs, config, &expr);
136 ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
137 ast::Expr::MethodCallExpr(it) => {
138 param_name_hints(hints, sema, config, ast::Expr::from(it))
140 ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, it),
141 // We could show reborrows for all expressions, but usually that is just noise to the user
142 // and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
143 ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
148 binding_mode_hints(hints, sema, config, &it);
149 if let ast::Pat::IdentPat(it) = it {
150 bind_pat_hints(hints, sema, config, &it);
154 ast::Fn(it) => lifetime_fn_hints(hints, config, it),
160 fn closing_brace_hints(
161 acc: &mut Vec<InlayHint>,
162 sema: &Semantics<RootDatabase>,
163 config: &InlayHintsConfig,
166 let min_lines = config.closing_brace_hints_min_lines?;
168 let name = |it: ast::Name| it.syntax().text_range().start();
170 let mut closing_token;
171 let (label, name_offset) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
172 closing_token = item_list.r_curly_token()?;
174 let parent = item_list.syntax().parent()?;
178 let imp = sema.to_def(&imp)?;
179 let ty = imp.self_ty(sema.db);
180 let trait_ = imp.trait_(sema.db);
183 Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
184 None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
188 (format!("trait {}", tr.name()?), tr.name().map(name))
193 } else if let Some(list) = ast::ItemList::cast(node.clone()) {
194 closing_token = list.r_curly_token()?;
196 let module = ast::Module::cast(list.syntax().parent()?)?;
197 (format!("mod {}", module.name()?), module.name().map(name))
198 } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
199 closing_token = block.stmt_list()?.r_curly_token()?;
201 let parent = block.syntax().parent()?;
205 // FIXME: this could include parameters, but `HirDisplay` prints too much info
206 // and doesn't respect the max length either, so the hints end up way too long
207 (format!("fn {}", it.name()?), it.name().map(name))
209 ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
211 if it.underscore_token().is_some() {
212 ("const _".into(), None)
214 (format!("const {}", it.name()?), it.name().map(name))
220 } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
221 let last_token = mac.syntax().last_token()?;
222 if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
225 closing_token = last_token;
228 format!("{}!", mac.path()?),
229 mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range().start()),
235 if let Some(mut next) = closing_token.next_token() {
236 if next.kind() == T![;] {
237 if let Some(tok) = next.next_token() {
238 closing_token = next;
242 if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
243 // Only display the hint if the `}` is the last token on the line
249 node.text().for_each_chunk(|s| lines += s.matches('\n').count());
250 if lines < min_lines {
255 range: closing_token.text_range(),
256 kind: InlayKind::ClosingBraceHint(name_offset),
263 fn lifetime_fn_hints(
264 acc: &mut Vec<InlayHint>,
265 config: &InlayHintsConfig,
268 if config.lifetime_elision_hints == LifetimeElisionHints::Never {
272 let mk_lt_hint = |t: SyntaxToken, label| InlayHint {
273 range: t.text_range(),
274 kind: InlayKind::LifetimeHint,
278 let param_list = func.param_list()?;
279 let generic_param_list = func.generic_param_list();
280 let ret_type = func.ret_type();
281 let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
283 let is_elided = |lt: &Option<ast::Lifetime>| match lt {
284 Some(lt) => matches!(lt.text().as_str(), "'_"),
288 let potential_lt_refs = {
289 let mut acc: Vec<_> = vec![];
290 if let Some(self_param) = &self_param {
291 let lifetime = self_param.lifetime();
292 let is_elided = is_elided(&lifetime);
293 acc.push((None, self_param.amp_token(), lifetime, is_elided));
295 param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
296 // FIXME: check path types
297 walk_ty(&ty, &mut |ty| match ty {
298 ast::Type::RefType(r) => {
299 let lifetime = r.lifetime();
300 let is_elided = is_elided(&lifetime);
302 pat.as_ref().and_then(|it| match it {
303 ast::Pat::IdentPat(p) => p.name(),
318 let mut gen_idx_name = {
319 let mut gen = (0u8..).map(|idx| match idx {
320 idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
321 idx => format!("'{idx}").into(),
323 move || gen.next().unwrap_or_default()
325 let mut allocated_lifetimes = vec![];
327 let mut used_names: FxHashMap<SmolStr, usize> =
328 match config.param_names_for_lifetime_elision_hints {
329 true => generic_param_list
331 .flat_map(|gpl| gpl.lifetime_params())
332 .filter_map(|param| param.lifetime())
333 .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
335 false => Default::default(),
338 let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
339 if let Some(_) = &self_param {
340 if let Some(_) = potential_lt_refs.next() {
341 allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
342 // self can't be used as a lifetime, so no need to check for collisions
349 potential_lt_refs.for_each(|(name, ..)| {
350 let name = match name {
351 Some(it) if config.param_names_for_lifetime_elision_hints => {
352 if let Some(c) = used_names.get_mut(it.text().as_str()) {
354 SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
356 used_names.insert(it.text().as_str().into(), 0);
357 SmolStr::from_iter(["\'", it.text().as_str()])
362 allocated_lifetimes.push(name);
366 // fetch output lifetime if elision rule applies
367 let output = match potential_lt_refs.as_slice() {
368 [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
370 Some(lt) => match lt.text().as_str() {
371 "'_" => allocated_lifetimes.get(0).cloned(),
373 name => Some(name.into()),
375 None => allocated_lifetimes.get(0).cloned(),
381 if allocated_lifetimes.is_empty() && output.is_none() {
386 // apply output if required
387 let mut is_trivial = true;
388 if let (Some(output_lt), Some(r)) = (&output, ret_type) {
389 if let Some(ty) = r.ty() {
390 walk_ty(&ty, &mut |ty| match ty {
391 ast::Type::RefType(ty) if ty.lifetime().is_none() => {
392 if let Some(amp) = ty.amp_token() {
394 acc.push(mk_lt_hint(amp, output_lt.to_string()));
402 if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
406 let mut a = allocated_lifetimes.iter();
407 for (_, amp_token, _, is_elided) in potential_lt_refs {
411 acc.push(mk_lt_hint(t, lt.to_string()));
415 // generate generic param list things
416 match (generic_param_list, allocated_lifetimes.as_slice()) {
418 (Some(gpl), allocated_lifetimes) => {
419 let angle_tok = gpl.l_angle_token()?;
420 let is_empty = gpl.generic_params().next().is_none();
425 allocated_lifetimes.iter().format(", "),
426 if is_empty { "" } else { ", " }
430 (None, allocated_lifetimes) => acc.push(InlayHint {
431 range: func.name()?.syntax().text_range(),
432 kind: InlayKind::GenericParamListHint,
433 label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
439 fn closure_ret_hints(
440 acc: &mut Vec<InlayHint>,
441 sema: &Semantics<RootDatabase>,
442 famous_defs: &FamousDefs,
443 config: &InlayHintsConfig,
444 closure: ast::ClosureExpr,
446 if !config.closure_return_type_hints {
450 let param_list = match closure.body() {
451 Some(ast::Expr::BlockExpr(_)) => closure.param_list()?,
455 let closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
456 let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
457 let callable = ty.as_callable(sema.db)?;
458 let ty = callable.return_type();
463 range: param_list.syntax().text_range(),
464 kind: InlayKind::ClosureReturnTypeHint,
465 label: hint_iterator(sema, &famous_defs, config, &ty)
466 .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string()),
472 acc: &mut Vec<InlayHint>,
473 sema: &Semantics<RootDatabase>,
474 config: &InlayHintsConfig,
477 if config.reborrow_hints == ReborrowHints::Never {
481 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
482 let desc_expr = descended.as_ref().unwrap_or(expr);
483 let mutability = sema.is_implicit_reborrow(desc_expr)?;
484 let label = match mutability {
485 hir::Mutability::Shared if config.reborrow_hints != ReborrowHints::MutableOnly => "&*",
486 hir::Mutability::Mut => "&mut *",
490 range: expr.syntax().text_range(),
491 kind: InlayKind::ImplicitReborrowHint,
492 label: label.to_string(),
498 acc: &mut Vec<InlayHint>,
499 sema: &Semantics<RootDatabase>,
500 famous_defs: &FamousDefs,
501 config: &InlayHintsConfig,
504 if !config.chaining_hints {
508 if matches!(expr, ast::Expr::RecordExpr(_)) {
512 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
513 let desc_expr = descended.as_ref().unwrap_or(expr);
515 let mut tokens = expr
517 .siblings_with_tokens(Direction::Next)
518 .filter_map(NodeOrToken::into_token)
519 .filter(|t| match t.kind() {
520 SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
521 SyntaxKind::COMMENT => false,
525 // Chaining can be defined as an expression whose next sibling tokens are newline and dot
526 // Ignoring extra whitespace and comments
527 let next = tokens.next()?.kind();
528 if next == SyntaxKind::WHITESPACE {
529 let mut next_next = tokens.next()?.kind();
530 while next_next == SyntaxKind::WHITESPACE {
531 next_next = tokens.next()?.kind();
533 if next_next == T![.] {
534 let ty = sema.type_of_expr(desc_expr)?.original;
538 if matches!(expr, ast::Expr::PathExpr(_)) {
539 if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
540 if st.fields(sema.db).is_empty() {
546 range: expr.syntax().text_range(),
547 kind: InlayKind::ChainingHint,
548 label: hint_iterator(sema, &famous_defs, config, &ty).unwrap_or_else(|| {
549 ty.display_truncated(sema.db, config.max_length).to_string()
558 acc: &mut Vec<InlayHint>,
559 sema: &Semantics<RootDatabase>,
560 config: &InlayHintsConfig,
563 if !config.parameter_hints {
567 let (callable, arg_list) = get_callable(sema, &expr)?;
571 .zip(arg_list.args())
572 .filter_map(|((param, _ty), arg)| {
573 // Only annotate hints for expressions that exist in the original file
574 let range = sema.original_range_opt(arg.syntax())?;
575 let param_name = match param? {
576 Either::Left(_) => "self".to_string(),
577 Either::Right(pat) => match pat {
578 ast::Pat::IdentPat(it) => it.name()?.to_string(),
582 Some((param_name, arg, range))
584 .filter(|(param_name, arg, _)| {
585 !should_hide_param_name_hint(sema, &callable, param_name, arg)
587 .map(|(param_name, _, FileRange { range, .. })| InlayHint {
589 kind: InlayKind::ParameterHint,
590 label: param_name.into(),
597 fn binding_mode_hints(
598 acc: &mut Vec<InlayHint>,
599 sema: &Semantics<RootDatabase>,
600 config: &InlayHintsConfig,
603 if !config.binding_mode_hints {
607 let range = pat.syntax().text_range();
608 sema.pattern_adjustments(&pat).iter().for_each(|ty| {
609 let reference = ty.is_reference();
610 let mut_reference = ty.is_mutable_reference();
611 let r = match (reference, mut_reference) {
612 (true, true) => "&mut",
613 (true, false) => "&",
616 acc.push(InlayHint { range, kind: InlayKind::BindingModeHint, label: r.to_string() });
619 ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
620 let bm = sema.binding_mode_of_pat(pat)?;
622 hir::BindingMode::Move => return None,
623 hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
624 hir::BindingMode::Ref(Mutability::Shared) => "ref",
626 acc.push(InlayHint { range, kind: InlayKind::BindingModeHint, label: bm.to_string() });
635 acc: &mut Vec<InlayHint>,
636 sema: &Semantics<RootDatabase>,
637 config: &InlayHintsConfig,
640 if !config.type_hints {
644 let descended = sema.descend_node_into_attributes(pat.clone()).pop();
645 let desc_pat = descended.as_ref().unwrap_or(pat);
646 let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
648 if should_not_display_type_hint(sema, pat, &ty) {
652 let krate = sema.scope(desc_pat.syntax())?.krate();
653 let famous_defs = FamousDefs(sema, krate);
654 let label = hint_iterator(sema, &famous_defs, config, &ty);
656 let label = match label {
657 Some(label) => label,
659 let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
660 if config.hide_named_constructor_hints
661 && is_named_constructor(sema, pat, &ty_name).is_some()
670 range: match pat.name() {
671 Some(name) => name.syntax().text_range(),
672 None => pat.syntax().text_range(),
674 kind: InlayKind::TypeHint,
681 fn is_named_constructor(
682 sema: &Semantics<RootDatabase>,
686 let let_node = pat.syntax().parent()?;
687 let expr = match_ast! {
689 ast::LetStmt(it) => it.initializer(),
690 ast::LetExpr(it) => it.expr(),
695 let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
696 // unwrap postfix expressions
697 let expr = match expr {
698 ast::Expr::TryExpr(it) => it.expr(),
699 ast::Expr::AwaitExpr(it) => it.expr(),
702 let expr = match expr {
703 ast::Expr::CallExpr(call) => match call.expr()? {
704 ast::Expr::PathExpr(path) => path,
707 ast::Expr::PathExpr(path) => path,
710 let path = expr.path()?;
712 let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
713 let callable_kind = callable.map(|it| it.kind());
714 let qual_seg = match callable_kind {
715 Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
716 path.qualifier()?.segment()
721 let ctor_name = match qual_seg.kind()? {
722 ast::PathSegmentKind::Name(name_ref) => {
723 match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
724 Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
725 None => name_ref.to_string(),
728 ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
731 (ctor_name == ty_name).then(|| ())
734 /// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
736 sema: &Semantics<RootDatabase>,
737 famous_defs: &FamousDefs,
738 config: &InlayHintsConfig,
740 ) -> Option<String> {
742 let strukt = ty.strip_references().as_adt()?;
743 let krate = strukt.module(db).krate();
744 if krate != famous_defs.core()? {
747 let iter_trait = famous_defs.core_iter_Iterator()?;
748 let iter_mod = famous_defs.core_iter()?;
750 // Assert that this struct comes from `core::iter`.
751 if !(strukt.visibility(db) == hir::Visibility::Public
752 && strukt.module(db).path_to_root(db).contains(&iter_mod))
757 if ty.impls_trait(db, iter_trait, &[]) {
758 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
759 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
762 if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
763 const LABEL_START: &str = "impl Iterator<Item = ";
764 const LABEL_END: &str = ">";
766 let ty_display = hint_iterator(sema, famous_defs, config, &ty)
767 .map(|assoc_type_impl| assoc_type_impl.to_string())
769 ty.display_truncated(
773 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
777 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
784 fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
785 if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
786 let pat_text = bind_pat.to_string();
790 .map(|variant| variant.name(db).to_smol_str())
791 .any(|enum_name| enum_name == pat_text)
797 fn should_not_display_type_hint(
798 sema: &Semantics<RootDatabase>,
799 bind_pat: &ast::IdentPat,
804 if pat_ty.is_unknown() {
808 if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
809 if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
814 for node in bind_pat.syntax().ancestors() {
817 ast::LetStmt(it) => return it.ty().is_some(),
818 // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
819 ast::Param(it) => return it.ty().is_some(),
820 ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
821 ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
822 ast::IfExpr(_) => return false,
823 ast::WhileExpr(_) => return false,
824 ast::ForExpr(it) => {
825 // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
826 // Type of expr should be iterable.
827 return it.in_token().is_none() ||
829 .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
830 .map(TypeInfo::original)
831 .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
840 fn should_hide_param_name_hint(
841 sema: &Semantics<RootDatabase>,
842 callable: &hir::Callable,
844 argument: &ast::Expr,
846 // These are to be tested in the `parameter_hint_heuristics` test
848 // - the parameter name is a suffix of the function's name
849 // - the argument is an enum whose name is equal to the parameter
850 // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
851 // of argument with _ splitting it off
852 // - param starts with `ra_fixture`
853 // - param is a well known name in a unary function
855 let param_name = param_name.trim_start_matches('_');
856 if param_name.is_empty() {
860 if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
864 let fn_name = match callable.kind() {
865 hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
868 let fn_name = fn_name.as_deref();
869 is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
870 || is_enum_name_similar_to_param_name(sema, argument, param_name)
871 || is_argument_similar_to_param_name(argument, param_name)
872 || param_name.starts_with("ra_fixture")
873 || (callable.n_params() == 1 && is_obvious_param(param_name))
876 fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
877 // check whether param_name and argument are the same or
878 // whether param_name is a prefix/suffix of argument(split at `_`)
879 let argument = match get_string_representation(argument) {
880 Some(argument) => argument,
881 None => return false,
884 // std is honestly too panic happy...
885 let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
887 let param_name = param_name.trim_start_matches('_');
888 let argument = argument.trim_start_matches('_');
890 match str_split_at(argument, param_name.len()) {
891 Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
892 return rest.is_empty() || rest.starts_with('_');
896 match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
897 Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
898 return rest.is_empty() || rest.ends_with('_');
905 /// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
907 /// `fn strip_suffix(suffix)` will be hidden.
908 /// `fn stripsuffix(suffix)` will not be hidden.
909 fn is_param_name_suffix_of_fn_name(
912 fn_name: Option<&str>,
914 match (callable.n_params(), fn_name) {
915 (1, Some(function)) => {
916 function == param_name
919 .checked_sub(param_name.len())
920 .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
921 .map_or(false, |(prefix, suffix)| {
922 suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
929 fn is_enum_name_similar_to_param_name(
930 sema: &Semantics<RootDatabase>,
931 argument: &ast::Expr,
934 match sema.type_of_expr(argument).and_then(|t| t.original.as_adt()) {
935 Some(hir::Adt::Enum(e)) => {
936 to_lower_snake_case(&e.name(sema.db).to_smol_str()) == param_name
942 fn get_string_representation(expr: &ast::Expr) -> Option<String> {
944 ast::Expr::MethodCallExpr(method_call_expr) => {
945 let name_ref = method_call_expr.name_ref()?;
946 match name_ref.text().as_str() {
947 "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
948 name_ref => Some(name_ref.to_owned()),
951 ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
952 ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
953 ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
954 ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
955 ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
960 fn is_obvious_param(param_name: &str) -> bool {
961 // avoid displaying hints for common functions like map, filter, etc.
962 // or other obvious words used in std
963 let is_obvious_param_name =
964 matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
965 param_name.len() == 1 || is_obvious_param_name
969 sema: &Semantics<RootDatabase>,
971 ) -> Option<(hir::Callable, ast::ArgList)> {
973 ast::Expr::CallExpr(expr) => {
974 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
975 let expr = descended.as_ref().unwrap_or(expr);
976 sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
978 ast::Expr::MethodCallExpr(expr) => {
979 let descended = sema.descend_node_into_attributes(expr.clone()).pop();
980 let expr = descended.as_ref().unwrap_or(expr);
981 sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
989 use expect_test::{expect, Expect};
990 use ide_db::base_db::FileRange;
991 use itertools::Itertools;
992 use syntax::{TextRange, TextSize};
993 use test_utils::extract_annotations;
995 use crate::inlay_hints::ReborrowHints;
996 use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
998 const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
999 render_colons: false,
1001 parameter_hints: false,
1002 chaining_hints: false,
1003 lifetime_elision_hints: LifetimeElisionHints::Never,
1004 closure_return_type_hints: false,
1005 reborrow_hints: ReborrowHints::Always,
1006 binding_mode_hints: false,
1007 hide_named_constructor_hints: false,
1008 param_names_for_lifetime_elision_hints: false,
1010 closing_brace_hints_min_lines: None,
1012 const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
1014 parameter_hints: true,
1015 chaining_hints: true,
1016 reborrow_hints: ReborrowHints::Always,
1017 closure_return_type_hints: true,
1018 binding_mode_hints: true,
1019 lifetime_elision_hints: LifetimeElisionHints::Always,
1024 fn check(ra_fixture: &str) {
1025 check_with_config(TEST_CONFIG, ra_fixture);
1029 fn check_params(ra_fixture: &str) {
1031 InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
1037 fn check_types(ra_fixture: &str) {
1038 check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
1042 fn check_chains(ra_fixture: &str) {
1043 check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
1047 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
1048 let (analysis, file_id) = fixture::file(ra_fixture);
1049 let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
1050 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1051 let actual = inlay_hints
1053 .map(|it| (it.range, it.label.to_string()))
1054 .sorted_by_key(|(range, _)| range.start())
1055 .collect::<Vec<_>>();
1056 expected.sort_by_key(|(range, _)| range.start());
1058 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
1062 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
1063 let (analysis, file_id) = fixture::file(ra_fixture);
1064 let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
1065 expect.assert_debug_eq(&inlay_hints)
1069 fn hints_disabled() {
1071 InlayHintsConfig { render_colons: true, ..DISABLED_CONFIG },
1073 fn foo(a: i32, b: i32) -> i32 { a + b }
1080 // Parameter hint tests
1083 fn param_hints_only() {
1086 fn foo(a: i32, b: i32) -> i32 { a + b }
1099 fn param_name_similar_to_fn_name_still_hints() {
1102 fn max(x: i32, y: i32) -> i32 { x + y }
1115 fn param_name_similar_to_fn_name() {
1118 fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
1120 let _x = param_with_underscore(
1127 fn param_with_underscore(underscore: i32) -> i32 { underscore }
1129 let _x = param_with_underscore(
1137 fn param_name_same_as_fn_name() {
1140 fn foo(foo: i32) -> i32 { foo }
1150 fn never_hide_param_when_multiple_params() {
1153 fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
1166 fn param_hints_look_through_as_ref_and_clone() {
1169 fn foo(bar: i32, baz: f32) {}
1175 foo(bar.clone(), bar.clone());
1177 foo(bar.as_ref(), bar.as_ref());
1185 fn self_param_hints() {
1191 fn foo(self: Self) {}
1192 fn bar(self: &Self) {}
1206 fn param_name_hints_show_for_literals() {
1208 r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
1221 fn function_call_parameter_hint() {
1224 //- minicore: option
1229 struct SyntaxKind {}
1230 struct NavigationTarget {}
1235 fn method(&self, mut param: i32) -> i32 { param * 2 }
1240 focus_range: Option<TextRange>,
1241 full_range: TextRange,
1243 docs: Option<String>,
1244 ) -> NavigationTarget {
1249 fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
1254 let not_literal = 1;
1255 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
1256 //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
1257 let t: Test = Test {};
1260 Test::method(&t, 3456);
1261 //^^ self ^^^^ param
1266 //^^^^^^^^^^^^^ name
1270 //^^^^^^^^^^^^ full_range
1272 //^^^^^^^^^^^^^ kind
1281 fn parameter_hint_heuristics() {
1284 fn check(ra_fixture_thing: &str) {}
1287 fn filter(predicate: i32) {}
1289 fn strip_suffix(suffix: &str) {}
1290 fn stripsuffix(suffix: &str) {}
1291 fn same(same: u32) {}
1292 fn same2(_same2: u32) {}
1294 fn enum_matches_param_name(completion_kind: CompletionKind) {}
1296 fn foo(param: u32) {}
1297 fn bar(param_eter: u32) {}
1299 enum CompletionKind {
1303 fn non_ident_pat((a, b): (u32, u32)) {}
1306 const PARAM: u32 = 0;
1321 enum_matches_param_name(CompletionKind::Keyword);
1328 let start_param = 0;
1336 let param_eter_end = 0;
1337 bar(param_eter_end);
1338 let start_param_eter = 0;
1339 bar(start_param_eter);
1340 let param_eter2 = 0;
1342 //^^^^^^^^^^^ param_eter
1344 non_ident_pat((0, 0));
1352 fn type_hints_only() {
1355 fn foo(a: i32, b: i32) -> i32 { a + b }
1364 fn type_hints_bindings_after_at() {
1367 //- minicore: option
1369 let ref foo @ bar @ ref mut baz = 0;
1375 if let x @ Some(_) = Some(0) {}
1377 let foo @ (bar, baz) = (3, 3);
1386 fn default_generic_types_should_not_be_displayed() {
1389 struct Test<K, T = u8> { k: K, t: T }
1392 let zz = Test { t: 23u8, k: 33 };
1397 //^^^^ || -> Test<i32>
1403 fn shorten_iterators_in_associated_params() {
1406 //- minicore: iterators
1409 pub struct SomeIter<T> {}
1411 impl<T> SomeIter<T> {
1412 pub fn new() -> Self { SomeIter {} }
1413 pub fn push(&mut self, t: T) {}
1416 impl<T> Iterator for SomeIter<T> {
1418 fn next(&mut self) -> Option<Self::Item> {
1424 let mut some_iter = SomeIter::new();
1425 //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
1426 some_iter.push(iter::repeat(2).take(2));
1427 let iter_of_iters = some_iter.take(2);
1428 //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
1435 fn infer_call_method_return_associated_types_with_generic() {
1439 fn default() -> Self;
1445 pub fn quux<T: Foo>() -> T::Bar {
1446 let y = Default::default();
1459 //- minicore: fn, sized
1460 fn foo() -> impl Fn() { loop {} }
1461 fn foo1() -> impl Fn(f64) { loop {} }
1462 fn foo2() -> impl Fn(f64, f64) { loop {} }
1463 fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
1464 fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
1465 fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
1466 fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
1467 fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
1475 // ^^^ impl Fn(f64, f64)
1477 // ^^^ impl Fn(f64, f64) -> u32
1479 // ^^^ &dyn Fn(f64, f64) -> u32
1481 // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
1483 // ^^^ impl Fn(f64, f64) -> u32
1485 // ^^^ *const impl Fn(f64, f64) -> u32
1492 fn check_hint_range_limit() {
1494 //- minicore: fn, sized
1495 fn foo() -> impl Fn() { loop {} }
1496 fn foo1() -> impl Fn(f64) { loop {} }
1497 fn foo2() -> impl Fn(f64, f64) { loop {} }
1498 fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
1499 fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
1500 fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
1501 fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
1502 fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
1508 // ^^^ impl Fn(f64, f64)
1510 // ^^^ impl Fn(f64, f64) -> u32
1517 let (analysis, file_id) = fixture::file(fixture);
1518 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
1519 let inlay_hints = analysis
1521 &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
1525 range: TextRange::new(TextSize::from(500), TextSize::from(600)),
1530 inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
1531 assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
1535 fn fn_hints_ptr_rpit_fn_parentheses() {
1538 //- minicore: fn, sized
1541 fn foo1() -> *const impl Fn() { loop {} }
1542 fn foo2() -> *const (impl Fn() + Sized) { loop {} }
1543 fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
1544 fn foo4() -> *const (impl Sized + Fn()) { loop {} }
1545 fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
1546 fn foo6() -> *const (impl Fn() + Trait) { loop {} }
1547 fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
1548 fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
1549 fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
1550 fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
1554 // ^^^ *const impl Fn()
1556 // ^^^ *const impl Fn()
1558 // ^^^ *const (impl Fn() + ?Sized)
1560 // ^^^ *const impl Fn()
1562 // ^^^ *const (impl Fn() + ?Sized)
1564 // ^^^ *const (impl Fn() + Trait)
1566 // ^^^ *const (impl Fn() + Trait)
1568 // ^^^ *const (impl Fn() + Trait + ?Sized)
1570 // ^^^ *const (impl Fn() -> u8 + ?Sized)
1572 // ^^^ *const impl Fn()
1579 fn unit_structs_have_no_type_hints() {
1582 //- minicore: result
1583 struct SyntheticSyntax;
1588 Err(SyntheticSyntax) => (),
1595 fn let_statement() {
1598 #[derive(PartialEq)]
1599 enum Option<T> { None, Some(T) }
1601 #[derive(PartialEq)]
1602 struct Test { a: Option<u32>, b: u8 }
1605 struct InnerStruct {}
1615 let test = InnerStruct {};
1618 let test = unresolved();
1620 let test = (42, 'a');
1622 let (a, (b, (c,)) = (2, (3, (9.2,));
1634 //- minicore: option
1635 struct Test { a: Option<u32>, b: u8 }
1638 let test = Some(Test { a: Some(3), b: 1 });
1640 if let None = &test {};
1641 if let test = &test {};
1642 //^^^^ &Option<Test>
1643 if let Some(test) = &test {};
1645 if let Some(Test { a, b }) = &test {};
1646 //^ &Option<u32> ^ &u8
1647 if let Some(Test { a: x, b: y }) = &test {};
1648 //^ &Option<u32> ^ &u8
1649 if let Some(Test { a: Some(x), b: y }) = &test {};
1651 if let Some(Test { a: None, b: y }) = &test {};
1653 if let Some(Test { b: y, .. }) = &test {};
1664 //- minicore: option
1665 struct Test { a: Option<u32>, b: u8 }
1668 let test = Some(Test { a: Some(3), b: 1 });
1670 while let Some(Test { a: Some(x), b: y }) = &test {};
1677 fn match_arm_list() {
1680 //- minicore: option
1681 struct Test { a: Option<u32>, b: u8 }
1684 match Some(Test { a: Some(3), b: 1 }) {
1688 Some(Test { a: Some(x), b: y }) => (),
1697 fn complete_for_hint() {
1700 //- minicore: iterator
1701 pub struct Vec<T> {}
1704 pub fn new() -> Self { Vec {} }
1705 pub fn push(&mut self, t: T) {}
1708 impl<T> IntoIterator for Vec<T> {
1713 let mut data = Vec::new();
1727 fn multi_dyn_trait_bounds() {
1730 pub struct Vec<T> {}
1733 pub fn new() -> Self { Vec {} }
1736 pub struct Box<T> {}
1742 // The block expression wrapping disables the constructor hint hiding logic
1743 let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
1744 //^^ Vec<Box<&(dyn Display + Sync)>>
1745 let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
1746 //^^ Vec<Box<*const (dyn Display + Sync)>>
1747 let _v = { Vec::<Box<dyn Display + Sync>>::new() };
1748 //^^ Vec<Box<dyn Display + Sync>>
1755 fn shorten_iterator_hints() {
1758 //- minicore: iterators
1763 impl Iterator for MyIter {
1765 fn next(&mut self) -> Option<Self::Item> {
1773 let _x = iter::repeat(0);
1774 //^^ impl Iterator<Item = i32>
1775 fn generic<T: Clone>(t: T) {
1776 let _x = iter::repeat(t);
1777 //^^ impl Iterator<Item = T>
1778 let _chained = iter::repeat(t).take(10);
1779 //^^^^^^^^ impl Iterator<Item = T>
1787 fn skip_constructor_and_enum_type_hints() {
1791 hide_named_constructor_hints: true,
1795 //- minicore: try, option
1796 use core::ops::ControlFlow;
1799 pub mod y { pub struct Foo; }
1801 pub enum AnotherEnum {
1806 struct TupleStruct();
1812 fn try_new() -> ControlFlow<(), Self> {
1813 ControlFlow::Continue(Struct)
1817 struct Generic<T>(T);
1828 fn times2(value: i32) -> i32 {
1833 let enumb = Enum::Variant(0);
1835 let strukt = x::Foo;
1836 let strukt = x::y::Foo;
1837 let strukt = Struct;
1838 let strukt = Struct::new();
1840 let tuple_struct = TupleStruct();
1842 let generic0 = Generic::new();
1843 // ^^^^^^^^ Generic<i32>
1844 let generic1 = Generic(0);
1845 // ^^^^^^^^ Generic<i32>
1846 let generic2 = Generic::<i32>::new();
1847 let generic3 = <Generic<i32>>::new();
1848 let generic4 = Generic::<i32>(0);
1851 let option = Some(0);
1852 // ^^^^^^ Option<i32>
1854 // ^^^^ fn times2(i32) -> i32
1855 let closure = |x: i32| x * 2;
1856 // ^^^^^^^ |i32| -> i32
1859 fn fallible() -> ControlFlow<()> {
1860 let strukt = Struct::try_new()?;
1867 fn shows_constructor_type_hints_when_enabled() {
1871 use core::ops::ControlFlow;
1874 struct TupleStruct();
1880 fn try_new() -> ControlFlow<(), Self> {
1881 ControlFlow::Continue(Struct)
1885 struct Generic<T>(T);
1893 let strukt = Struct::new();
1895 let tuple_struct = TupleStruct();
1896 // ^^^^^^^^^^^^ TupleStruct
1897 let generic0 = Generic::new();
1898 // ^^^^^^^^ Generic<i32>
1899 let generic1 = Generic::<i32>::new();
1900 // ^^^^^^^^ Generic<i32>
1901 let generic2 = <Generic<i32>>::new();
1902 // ^^^^^^^^ Generic<i32>
1905 fn fallible() -> ControlFlow<()> {
1906 let strukt = Struct::try_new()?;
1920 (0..2).for_each(|increment | { start += increment; });
1924 //^^^^^^^^ |i32, i32| -> i32
1930 let _: i32 = multiply(1, 2);
1931 let multiply_ref = &multiply;
1932 //^^^^^^^^^^^^ &|i32, i32| -> i32
1934 let return_42 = || 42;
1935 //^^^^^^^^^ || -> i32
1943 fn hint_truncation() {
1945 InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
1949 struct VeryLongOuterName<T>(T);
1954 let b = VeryLongOuterName(0usize);
1955 //^ VeryLongOuterName<…>
1956 let c = Smol(Smol(0u32))
1962 // Chaining hint tests
1965 fn chaining_hints_ignore_comments() {
1967 InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
1970 impl A { fn into_b(self) -> B { self.0 } }
1972 impl B { fn into_c(self) -> C { self.0 } }
1977 .into_b() // This is a comment
1978 // This is another comment
2000 fn chaining_hints_without_newlines() {
2004 impl A { fn into_b(self) -> B { self.0 } }
2006 impl B { fn into_c(self) -> C { self.0 } }
2010 let c = A(B(C)).into_b().into_c();
2016 fn struct_access_chaining_hints() {
2018 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2020 struct A { pub b: B }
2021 struct B { pub c: C }
2026 fn foo(&self) -> i32 { 42 }
2030 let x = A { b: B { c: C(true) } }
2055 fn generic_chaining_hints() {
2057 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2062 struct X<T,R>(T, R);
2065 fn new(t: T) -> Self { A(t) }
2066 fn into_b(self) -> B<T> { B(self.0) }
2069 fn into_c(self) -> C<T> { C(self.0) }
2072 let c = A::new(X(42, true))
2082 label: "B<X<i32, bool>>",
2087 label: "A<X<i32, bool>>",
2095 fn shorten_iterator_chaining_hints() {
2097 InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
2099 //- minicore: iterators
2104 impl Iterator for MyIter {
2106 fn next(&mut self) -> Option<Self::Item> {
2112 let _x = MyIter.by_ref()
2124 label: "impl Iterator<Item = ()>",
2129 label: "impl Iterator<Item = ()>",
2134 label: "impl Iterator<Item = ()>",
2139 label: "&mut MyIter",
2147 fn hints_in_attr_call() {
2151 //- proc_macros: identity, input_replace
2154 fn chain(self) -> Self {
2158 #[proc_macros::identity]
2160 let strukt = Struct;
2165 Struct::chain(strukt);
2187 kind: ParameterHint,
2196 fn hints_lifetimes() {
2201 fn no_gpl(a: &()) {}
2204 fn empty_gpl<>(a: &()) {}
2206 fn partial<'b>(a: &(), b: &'b ()) {}
2208 fn partial<'a>(a: &'a (), b: &()) {}
2211 fn single_ret(a: &()) -> &() {}
2214 fn full_mul(a: &(), b: &()) {}
2218 fn foo<'c>(a: &'c ()) -> &() {}
2221 fn nested_in(a: & &X< &()>) {}
2222 // ^^^^^^^^^<'0, '1, '2>
2224 fn nested_out(a: &()) -> & &X< &()>{}
2232 fn foo(&self) -> &() {}
2235 fn foo(&self, a: &()) -> &() {}
2244 fn hints_lifetimes_named() {
2246 InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
2248 fn nested_in<'named>(named: & &X< &()>) {}
2249 // ^'named1, 'named2, 'named3, $
2250 //^'named1 ^'named2 ^'named3
2256 fn hints_lifetimes_trivial_skip() {
2259 lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
2263 fn no_gpl(a: &()) {}
2264 fn empty_gpl<>(a: &()) {}
2265 fn partial<'b>(a: &(), b: &'b ()) {}
2266 fn partial<'a>(a: &'a (), b: &()) {}
2268 fn single_ret(a: &()) -> &() {}
2271 fn full_mul(a: &(), b: &()) {}
2273 fn foo<'c>(a: &'c ()) -> &() {}
2276 fn nested_in(a: & &X< &()>) {}
2277 fn nested_out(a: &()) -> & &X< &()>{}
2283 fn foo(&self) -> &() {}
2286 fn foo(&self, a: &()) -> &() {}
2295 fn hints_implicit_reborrow() {
2298 reborrow_hints: ReborrowHints::Always,
2299 parameter_hints: true,
2304 let unique = &mut ();
2306 let foo: &mut _ = unique;
2311 let shared = ref_id(unique);
2315 let r_mov: &_ = shared;
2322 fn identity<T>(t: T) -> T {
2325 fn ref_mut_id(mut_ref: &mut ()) -> &mut () {
2329 fn ref_id(shared_ref: &()) -> &() {
2337 fn hints_binding_modes() {
2339 InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
2354 let (x,) = &mut (0,);
2357 let &mut (x,) = &mut (0,);
2358 let (ref mut x,) = &mut (0,);
2360 let &mut (ref mut x,) = &mut (0,);
2361 let (mut x,) = &mut (0,);
2381 fn hints_closing_brace() {
2383 InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
2388 } // no hint unless `}` is the last token on the line
2394 fn h<T>(with: T, arguments: u8, ...) {