summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide')
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs27
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links.rs8
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/expand_macro.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/extend_selection.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs123
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/highlight_related.rs21
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover.rs101
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs109
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/tests.rs470
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs2930
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs630
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs978
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs142
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs665
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs196
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs49
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs142
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs325
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs75
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs546
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/markup.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/moniker.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/navigation_target.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs15
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/runnables.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/signature_help.rs60
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/static_index.rs13
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/status.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs3
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs20
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/typing.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs11
40 files changed, 4719 insertions, 2990 deletions
diff --git a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
index 5a8cda8fb..48bcd37b6 100644
--- a/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/call_hierarchy.rs
@@ -57,7 +57,8 @@ pub(crate) fn incoming_calls(
.flat_map(|func| func.usages(sema).all());
for (_, references) in references {
- let references = references.into_iter().map(|FileReference { name, .. }| name);
+ let references =
+ references.iter().filter_map(|FileReference { name, .. }| name.as_name_ref());
for name in references {
// This target is the containing function
let nav = sema.ancestors_with_macros(name.syntax().clone()).find_map(|node| {
@@ -457,4 +458,28 @@ fn caller$0() {
expect![[]],
);
}
+
+ #[test]
+ fn test_trait_method_call_hierarchy() {
+ check_hierarchy(
+ r#"
+trait T1 {
+ fn call$0ee();
+}
+
+struct S1;
+
+impl T1 for S1 {
+ fn callee() {}
+}
+
+fn caller() {
+ S1::callee();
+}
+"#,
+ expect![["callee Function FileId(0) 15..27 18..24"]],
+ expect![["caller Function FileId(0) 82..115 85..91 : [104..110]"]],
+ expect![[]],
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
index d96827326..b4a7f2b91 100644
--- a/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links.rs
@@ -273,7 +273,7 @@ impl DocCommentToken {
let (in_expansion_range, link, ns) =
extract_definitions_from_docs(&docs).into_iter().find_map(|(range, link, ns)| {
let mapped = doc_mapping.map(range)?;
- (mapped.value.contains(abs_in_expansion_offset)).then(|| (mapped.value, link, ns))
+ (mapped.value.contains(abs_in_expansion_offset)).then_some((mapped.value, link, ns))
})?;
// get the relative range to the doc/attribute in the expansion
let in_expansion_relative_range = in_expansion_range - descended_prefix_len - token_start;
@@ -285,7 +285,7 @@ impl DocCommentToken {
}
}
-fn broken_link_clone_cb<'a>(link: BrokenLink<'a>) -> Option<(CowStr<'a>, CowStr<'a>)> {
+fn broken_link_clone_cb(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
Some((/*url*/ link.reference.clone(), /*title*/ link.reference))
}
@@ -453,7 +453,7 @@ fn get_doc_base_url(db: &RootDatabase, def: Definition) -> Option<Url> {
})?
}
};
- Url::parse(&base).ok()?.join(&format!("{}/", display_name)).ok()
+ Url::parse(&base).ok()?.join(&format!("{display_name}/")).ok()
}
/// Get the filename and extension generated for a symbol by rustdoc.
@@ -488,7 +488,7 @@ fn filename_and_frag_for_def(
Some(kw) => {
format!("keyword.{}.html", kw.trim_matches('"'))
}
- None => format!("{}/index.html", name),
+ None => format!("{name}/index.html"),
},
None => String::from("index.html"),
},
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs
index 1df9aaae2..13088bdc3 100644
--- a/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/intra_doc_links.rs
@@ -63,8 +63,8 @@ mod tests {
fn check(link: &str, expected: Expect) {
let (l, a) = parse_intra_doc_link(link);
- let a = a.map_or_else(String::new, |a| format!(" ({:?})", a));
- expected.assert_eq(&format!("{}{}", l, a));
+ let a = a.map_or_else(String::new, |a| format!(" ({a:?})"));
+ expected.assert_eq(&format!("{l}{a}"));
}
#[test]
diff --git a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs
index c6bfb6b9d..104181a33 100644
--- a/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/doc_links/tests.rs
@@ -40,7 +40,7 @@ fn check_doc_links(ra_fixture: &str) {
.into_iter()
.map(|(_, link, ns)| {
let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns)
- .unwrap_or_else(|| panic!("Failed to resolve {}", link));
+ .unwrap_or_else(|| panic!("Failed to resolve {link}"));
let nav_target = def.try_to_nav(sema.db).unwrap();
let range =
FileRange { file_id: nav_target.file_id, range: nav_target.focus_or_full_range() };
diff --git a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
index 93252339c..418043d67 100644
--- a/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/expand_macro.rs
@@ -163,7 +163,7 @@ fn _format(
) -> Option<String> {
use ide_db::base_db::{FileLoader, SourceDatabase};
// hack until we get hygiene working (same character amount to preserve formatting as much as possible)
- const DOLLAR_CRATE_REPLACE: &str = &"__r_a_";
+ const DOLLAR_CRATE_REPLACE: &str = "__r_a_";
let expansion = expansion.replace("$crate", DOLLAR_CRATE_REPLACE);
let (prefix, suffix) = match kind {
SyntaxKind::MACRO_PAT => ("fn __(", ": u32);"),
diff --git a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs
index 45f1fd748..9f78c75e9 100644
--- a/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/extend_selection.rs
@@ -205,7 +205,7 @@ fn extend_single_word_in_comment_or_string(
}
let start_idx = before.rfind(non_word_char)? as u32;
- let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32;
+ let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
let from: TextSize = (start_idx + 1).into();
let to: TextSize = (cursor_position + end_idx).into();
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
index 926292c9b..c7130a2a4 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
@@ -1,18 +1,22 @@
-use hir::Semantics;
+use hir::{AsAssocItem, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
RootDatabase,
};
use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
-use crate::{FilePosition, NavigationTarget, RangeInfo};
+use crate::{
+ goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget,
+ RangeInfo,
+};
// Feature: Go to Declaration
//
// Navigates to the declaration of an identifier.
//
-// This is currently the same as `Go to Definition` with the exception of outline modules where it
-// will navigate to the `mod name;` item declaration.
+// This is the same as `Go to Definition` with the following exceptions:
+// - outline modules will navigate to the `mod name;` item declaration
+// - trait assoc items will navigate to the assoc item of the trait declaration opposed to the trait impl
pub(crate) fn goto_declaration(
db: &RootDatabase,
position: FilePosition,
@@ -32,25 +36,37 @@ pub(crate) fn goto_declaration(
match parent {
ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
NameRefClass::Definition(it) => Some(it),
- _ => None
+ NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
},
ast::Name(name) => match NameClass::classify(&sema, &name)? {
- NameClass::Definition(it) => Some(it),
- _ => None
+ NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
+ NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
},
_ => None
}
};
- match def? {
+ let assoc = match def? {
Definition::Module(module) => {
- Some(NavigationTarget::from_module_to_decl(db, module))
+ return Some(NavigationTarget::from_module_to_decl(db, module))
}
+ Definition::Const(c) => c.as_assoc_item(db),
+ Definition::TypeAlias(ta) => ta.as_assoc_item(db),
+ Definition::Function(f) => f.as_assoc_item(db),
_ => None,
- }
+ }?;
+
+ let trait_ = assoc.containing_trait_impl(db)?;
+ let name = Some(assoc.name(db)?);
+ let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
+ item.try_to_nav(db)
})
.collect();
- Some(RangeInfo::new(range, info))
+ if info.is_empty() {
+ goto_definition(db, position)
+ } else {
+ Some(RangeInfo::new(range, info))
+ }
}
#[cfg(test)]
@@ -109,4 +125,89 @@ mod foo {
"#,
)
}
+
+ #[test]
+ fn goto_decl_goto_def_fallback() {
+ check(
+ r#"
+struct Foo;
+ // ^^^
+impl Foo$0 {}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_decl_assoc_item_no_impl_item() {
+ check(
+ r#"
+trait Trait {
+ const C: () = ();
+ // ^
+}
+impl Trait for () {}
+
+fn main() {
+ <()>::C$0;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_decl_assoc_item() {
+ check(
+ r#"
+trait Trait {
+ const C: () = ();
+ // ^
+}
+impl Trait for () {
+ const C: () = ();
+}
+
+fn main() {
+ <()>::C$0;
+}
+"#,
+ );
+ check(
+ r#"
+trait Trait {
+ const C: () = ();
+ // ^
+}
+impl Trait for () {
+ const C$0: () = ();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_decl_field_pat_shorthand() {
+ check(
+ r#"
+struct Foo { field: u32 }
+ //^^^^^
+fn main() {
+ let Foo { field$0 };
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn goto_decl_constructor_shorthand() {
+ check(
+ r#"
+struct Foo { field: u32 }
+ //^^^^^
+fn main() {
+ let field = 0;
+ Foo { field$0 };
+}
+"#,
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
index 43f7a529b..73fd518a9 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -187,7 +187,7 @@ mod tests {
let (analysis, position) = fixture::position(ra_fixture);
let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
- assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {:?}", navs)
+ assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}")
}
#[test]
diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
index b3f711b6b..190ab80ba 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_implementation.rs
@@ -110,7 +110,7 @@ fn impls_for_trait_item(
.filter_map(|imp| {
let item = imp.items(sema.db).iter().find_map(|itm| {
let itm_name = itm.name(sema.db)?;
- (itm_name == fun_name).then(|| *itm)
+ (itm_name == fun_name).then_some(*itm)
})?;
item.try_to_nav(sema.db)
})
diff --git a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
index 540a11583..55f8779ee 100644
--- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
@@ -110,7 +110,7 @@ fn highlight_references(
.and_then(|decl| decl.focus_range)
.map(|range| {
let category =
- references::decl_mutability(&def, node, range).then(|| ReferenceCategory::Write);
+ references::decl_mutability(&def, node, range).then_some(ReferenceCategory::Write);
HighlightedRange { range, category }
});
if let Some(hl_range) = hl_range {
@@ -365,7 +365,7 @@ mod tests {
let mut expected = annotations
.into_iter()
- .map(|(r, access)| (r.range, (!access.is_empty()).then(|| access)))
+ .map(|(r, access)| (r.range, (!access.is_empty()).then_some(access)))
.collect::<Vec<_>>();
let mut actual = hls
@@ -766,6 +766,23 @@ fn foo() ->$0 u32 {
}
#[test]
+ fn test_hl_inner_tail_exit_points_loops() {
+ check(
+ r#"
+fn foo() ->$0 u32 {
+ 'foo: while { return 0; true } {
+ // ^^^^^^
+ break 'foo 0;
+ // ^^^^^
+ return 0;
+ // ^^^^^^
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
fn test_hl_break_loop() {
check(
r#"
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs
index 838fb18c3..b214fa12a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs
@@ -127,6 +127,7 @@ pub(crate) fn hover(
original_token.parent().and_then(ast::TokenTree::cast),
Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
);
+
// prefer descending the same token kind in attribute expansions, in normal macros text
// equivalency is more important
let descended = if in_attr {
@@ -135,54 +136,67 @@ pub(crate) fn hover(
sema.descend_into_macros_with_same_text(original_token.clone())
};
- // FIXME: Definition should include known lints and the like instead of having this special case here
- let hovered_lint = descended.iter().find_map(|token| {
- let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
- render::try_for_lint(&attr, token)
- });
- if let Some(res) = hovered_lint {
- return Some(RangeInfo::new(original_token.text_range(), res));
- }
-
+ // try lint hover
let result = descended
.iter()
- .filter_map(|token| {
- let node = token.parent()?;
- let class = IdentClass::classify_token(sema, token)?;
- if let IdentClass::Operator(OperatorClass::Await(_)) = class {
- // It's better for us to fall back to the keyword hover here,
- // rendering poll is very confusing
- return None;
- }
- Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ .find_map(|token| {
+ // FIXME: Definition should include known lints and the like instead of having this special case here
+ let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+ render::try_for_lint(&attr, token)
})
- .flatten()
- .unique_by(|&(def, _)| def)
- .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
- .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
- acc.actions.extend(actions);
- acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
- acc
- });
+ // try item definitions
+ .or_else(|| {
+ descended
+ .iter()
+ .filter_map(|token| {
+ let node = token.parent()?;
+ let class = IdentClass::classify_token(sema, token)?;
+ if let IdentClass::Operator(OperatorClass::Await(_)) = class {
+ // It's better for us to fall back to the keyword hover here,
+ // rendering poll is very confusing
+ return None;
+ }
+ Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ })
+ .flatten()
+ .unique_by(|&(def, _)| def)
+ .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+ .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
+ acc
+ })
+ })
+ // try keywords
+ .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
+ // try rest item hover
+ .or_else(|| {
+ descended.iter().find_map(|token| {
+ if token.kind() != DOT2 {
+ return None;
+ }
- if result.is_none() {
- // fallbacks, show keywords or types
+ let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
+ let record_pat_field_list =
+ rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
- let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
- if let Some(res) = res {
- return Some(RangeInfo::new(original_token.text_range(), res));
- }
- let res = descended
- .iter()
- .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
- if let Some(_) = res {
- return res;
- }
- }
- result.map(|mut res: HoverResult| {
- res.actions = dedupe_or_merge_hover_actions(res.actions);
- RangeInfo::new(original_token.text_range(), res)
- })
+ let record_pat =
+ record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
+
+ Some(render::struct_rest_pat(sema, config, &record_pat))
+ })
+ });
+
+ result
+ .map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
+ })
+ // fallback to type hover if there aren't any other suggestions
+ // this finds its own range instead of using the closest token's range
+ .or_else(|| {
+ descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token))
+ })
}
pub(crate) fn hover_for_definition(
@@ -269,6 +283,7 @@ fn hover_type_fallback(
};
let res = render::type_info(sema, config, &expr_or_pat)?;
+
let range = sema
.original_range_opt(&node)
.map(|frange| frange.range)
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
index d109c0769..47257f0bf 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -2,7 +2,9 @@
use std::fmt::Display;
use either::Either;
-use hir::{AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo};
+use hir::{
+ Adt, AsAssocItem, AttributeTemplate, HasAttrs, HasSource, HirDisplay, Semantics, TypeInfo,
+};
use ide_db::{
base_db::SourceDatabase,
defs::Definition,
@@ -14,7 +16,9 @@ use ide_db::{
use itertools::Itertools;
use stdx::format_to;
use syntax::{
- algo, ast, match_ast, AstNode, Direction,
+ algo,
+ ast::{self, RecordPat},
+ match_ast, AstNode, Direction,
SyntaxKind::{LET_EXPR, LET_STMT},
SyntaxToken, T,
};
@@ -250,6 +254,50 @@ pub(super) fn keyword(
Some(HoverResult { markup, actions })
}
+/// Returns missing types in a record pattern.
+/// Only makes sense when there's a rest pattern in the record pattern.
+/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
+pub(super) fn struct_rest_pat(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ pattern: &RecordPat,
+) -> HoverResult {
+ let missing_fields = sema.record_pattern_missing_fields(pattern);
+
+ // if there are no missing fields, the end result is a hover that shows ".."
+ // should be left in to indicate that there are no more fields in the pattern
+ // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ for (_, t) in &missing_fields {
+ walk_and_push_ty(sema.db, t, &mut push_new_def);
+ }
+
+ res.markup = {
+ let mut s = String::from(".., ");
+ for (f, _) in &missing_fields {
+ s += f.display(sema.db).to_string().as_ref();
+ s += ", ";
+ }
+ // get rid of trailing comma
+ s.truncate(s.len() - 2);
+
+ if config.markdown() {
+ Markup::fenced_block(&s)
+ } else {
+ s.into()
+ }
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ res
+}
+
pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
let (path, tt) = attr.as_simple_call()?;
if !tt.syntax().text_range().contains(token.text_range().start()) {
@@ -342,15 +390,35 @@ pub(super) fn definition(
let mod_path = definition_mod_path(db, &def);
let (label, docs) = match def {
Definition::Macro(it) => label_and_docs(db, it),
- Definition::Field(it) => label_and_docs(db, it),
+ Definition::Field(it) => label_and_layout_info_and_docs(db, it, |&it| {
+ let var_def = it.parent_def(db);
+ let id = it.index();
+ let layout = it.layout(db).ok()?;
+ let offset = match var_def {
+ hir::VariantDef::Struct(s) => Adt::from(s)
+ .layout(db)
+ .ok()
+ .map(|layout| format!(", offset = {}", layout.fields.offset(id).bytes())),
+ _ => None,
+ };
+ Some(format!(
+ "size = {}, align = {}{}",
+ layout.size.bytes(),
+ layout.align.abi.bytes(),
+ offset.as_deref().unwrap_or_default()
+ ))
+ }),
Definition::Module(it) => label_and_docs(db, it),
Definition::Function(it) => label_and_docs(db, it),
- Definition::Adt(it) => label_and_docs(db, it),
+ Definition::Adt(it) => label_and_layout_info_and_docs(db, it, |&it| {
+ let layout = it.layout(db).ok()?;
+ Some(format!("size = {}, align = {}", layout.size.bytes(), layout.align.abi.bytes()))
+ }),
Definition::Variant(it) => label_value_and_docs(db, it, |&it| {
if !it.parent_enum(db).is_data_carrying(db) {
match it.eval(db) {
- Ok(x) => Some(format!("{}", x)),
- Err(_) => it.value(db).map(|x| format!("{:?}", x)),
+ Ok(x) => Some(format!("{x}")),
+ Err(_) => it.value(db).map(|x| format!("{x:?}")),
}
} else {
None
@@ -359,7 +427,7 @@ pub(super) fn definition(
Definition::Const(it) => label_value_and_docs(db, it, |it| {
let body = it.eval(db);
match body {
- Ok(x) => Some(format!("{}", x)),
+ Ok(x) => Some(format!("{x}")),
Err(_) => {
let source = it.source(db)?;
let mut body = source.value.body()?.syntax().clone();
@@ -415,7 +483,7 @@ pub(super) fn definition(
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
let name = attr.name(db);
- let desc = format!("#[{}]", name);
+ let desc = format!("#[{name}]");
let AttributeTemplate { word, list, name_value_str } = match attr.template(db) {
Some(template) => template,
@@ -443,6 +511,25 @@ where
(label, docs)
}
+fn label_and_layout_info_and_docs<D, E, V>(
+ db: &RootDatabase,
+ def: D,
+ value_extractor: E,
+) -> (String, Option<hir::Documentation>)
+where
+ D: HasAttrs + HirDisplay,
+ E: Fn(&D) -> Option<V>,
+ V: Display,
+{
+ let label = if let Some(value) = value_extractor(&def) {
+ format!("{} // {value}", def.display(db))
+ } else {
+ def.display(db).to_string()
+ };
+ let docs = def.attrs(db).docs();
+ (label, docs)
+}
+
fn label_value_and_docs<D, E, V>(
db: &RootDatabase,
def: D,
@@ -454,7 +541,7 @@ where
V: Display,
{
let label = if let Some(value) = value_extractor(&def) {
- format!("{} = {}", def.display(db), value)
+ format!("{} = {value}", def.display(db))
} else {
def.display(db).to_string()
};
@@ -518,9 +605,9 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
} else {
""
};
- format!("{}{}{}: {}", let_kw, is_mut, name, ty)
+ format!("{let_kw}{is_mut}{name}: {ty}")
}
- Either::Right(_) => format!("{}self: {}", is_mut, ty),
+ Either::Right(_) => format!("{is_mut}self: {ty}"),
};
markup(None, desc, None)
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
index eb997e6fe..c7f241f2f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
@@ -37,7 +37,7 @@ fn check(ra_fixture: &str, expect: Expect) {
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
@@ -58,7 +58,7 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
@@ -79,7 +79,7 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
let content = analysis.db.file_text(position.file_id);
let hovered_element = &content[hover.range];
- let actual = format!("*{}*\n{}\n", hovered_element, hover.info.markup);
+ let actual = format!("*{hovered_element}*\n{}\n", hover.info.markup);
expect.assert_eq(&actual)
}
@@ -523,6 +523,27 @@ fn main() { }
}
#[test]
+fn hover_field_offset() {
+ // Hovering over the field when instantiating
+ check(
+ r#"
+struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
+"#,
+ expect![[r#"
+ *field_a*
+
+ ```rust
+ test::Foo
+ ```
+
+ ```rust
+ field_a: u8 // size = 1, align = 1, offset = 4
+ ```
+ "#]],
+ );
+}
+
+#[test]
fn hover_shows_struct_field_info() {
// Hovering over the field when instantiating
check(
@@ -534,16 +555,16 @@ fn main() {
}
"#,
expect![[r#"
- *field_a*
+ *field_a*
- ```rust
- test::Foo
- ```
+ ```rust
+ test::Foo
+ ```
- ```rust
- field_a: u32
- ```
- "#]],
+ ```rust
+ field_a: u32 // size = 4, align = 4, offset = 0
+ ```
+ "#]],
);
// Hovering over the field in the definition
@@ -556,16 +577,16 @@ fn main() {
}
"#,
expect![[r#"
- *field_a*
+ *field_a*
- ```rust
- test::Foo
- ```
+ ```rust
+ test::Foo
+ ```
- ```rust
- field_a: u32
- ```
- "#]],
+ ```rust
+ field_a: u32 // size = 4, align = 4, offset = 0
+ ```
+ "#]],
);
}
@@ -1508,30 +1529,30 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
- expect![[r##"
- *Bar*
+ expect![[r#"
+ *Bar*
- ```rust
- test
- ```
+ ```rust
+ test
+ ```
- ```rust
- struct Bar
- ```
+ ```rust
+ struct Bar // size = 0, align = 1
+ ```
- ---
+ ---
- This is an example
- multiline doc
+ This is an example
+ multiline doc
- # Example
+ # Example
- ```
- let five = 5;
+ ```
+ let five = 5;
- assert_eq!(6, my_crate::add_one(5));
- ```
- "##]],
+ assert_eq!(6, my_crate::add_one(5));
+ ```
+ "#]],
);
}
@@ -1545,20 +1566,20 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
- *Bar*
+ *Bar*
- ```rust
- test
- ```
+ ```rust
+ test
+ ```
- ```rust
- struct Bar
- ```
+ ```rust
+ struct Bar // size = 0, align = 1
+ ```
- ---
+ ---
- bar docs
- "#]],
+ bar docs
+ "#]],
);
}
@@ -1574,22 +1595,22 @@ struct Bar;
fn foo() { let bar = Ba$0r; }
"#,
expect![[r#"
- *Bar*
+ *Bar*
- ```rust
- test
- ```
+ ```rust
+ test
+ ```
- ```rust
- struct Bar
- ```
+ ```rust
+ struct Bar // size = 0, align = 1
+ ```
- ---
+ ---
- bar docs 0
- bar docs 1
- bar docs 2
- "#]],
+ bar docs 0
+ bar docs 1
+ bar docs 2
+ "#]],
);
}
@@ -1602,20 +1623,20 @@ pub struct Foo;
pub struct B$0ar
"#,
expect![[r#"
- *Bar*
+ *Bar*
- ```rust
- test
- ```
+ ```rust
+ test
+ ```
- ```rust
- pub struct Bar
- ```
+ ```rust
+ pub struct Bar // size = 0, align = 1
+ ```
- ---
+ ---
- [external](https://www.google.com)
- "#]],
+ [external](https://www.google.com)
+ "#]],
);
}
@@ -1629,20 +1650,20 @@ pub struct Foo;
pub struct B$0ar
"#,
expect![[r#"
- *Bar*
+ *Bar*
- ```rust
- test
- ```
+ ```rust
+ test
+ ```
- ```rust
- pub struct Bar
- ```
+ ```rust
+ pub struct Bar // size = 0, align = 1
+ ```
- ---
+ ---
- [baz](Baz)
- "#]],
+ [baz](Baz)
+ "#]],
);
}
@@ -2960,7 +2981,7 @@ fn main() {
```
```rust
- f: i32
+ f: i32 // size = 4, align = 4, offset = 0
```
"#]],
);
@@ -3636,6 +3657,163 @@ enum E {
#[test]
fn hover_const_eval() {
+ check(
+ r#"
+trait T {
+ const B: bool = false;
+}
+impl T for <()> {
+ /// true
+ const B: bool = true;
+}
+fn main() {
+ <()>::B$0;
+}
+"#,
+ expect![[r#"
+ *B*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const B: bool = true
+ ```
+
+ ---
+
+ true
+ "#]],
+ );
+
+ check(
+ r#"
+struct A {
+ i: i32
+};
+
+trait T {
+ const AA: A = A {
+ i: 1
+ };
+}
+impl T for i32 {
+ const AA: A = A {
+ i: 2
+ }
+}
+fn main() {
+ <i32>::AA$0;
+}
+"#,
+ expect![[r#"
+ *AA*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const AA: A = A {
+ i: 2
+ }
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+trait T {
+ /// false
+ const B: bool = false;
+}
+impl T for () {
+ /// true
+ const B: bool = true;
+}
+fn main() {
+ T::B$0;
+}
+"#,
+ expect![[r#"
+ *B*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const B: bool = false
+ ```
+
+ ---
+
+ false
+ "#]],
+ );
+
+ check(
+ r#"
+trait T {
+ /// false
+ const B: bool = false;
+}
+impl T for () {
+}
+fn main() {
+ <()>::B$0;
+}
+"#,
+ expect![[r#"
+ *B*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const B: bool = false
+ ```
+
+ ---
+
+ false
+ "#]],
+ );
+
+ check(
+ r#"
+trait T {
+ /// false
+ const B: bool = false;
+}
+impl T for () {
+ /// true
+ const B: bool = true;
+}
+impl T for i32 {}
+fn main() {
+ <i32>::B$0;
+}
+"#,
+ expect![[r#"
+ *B*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const B: bool = false
+ ```
+
+ ---
+
+ false
+ "#]],
+ );
+
// show hex for <10
check(
r#"
@@ -3902,6 +4080,37 @@ const FOO$0: f64 = 1.0f64;
}
#[test]
+fn hover_const_eval_in_generic_trait() {
+ // Doesn't compile, but we shouldn't crash.
+ check(
+ r#"
+trait Trait<T> {
+ const FOO: bool = false;
+}
+struct S<T>(T);
+impl<T> Trait<T> for S<T> {
+ const FOO: bool = true;
+}
+
+fn test() {
+ S::FOO$0;
+}
+"#,
+ expect![[r#"
+ *FOO*
+
+ ```rust
+ test
+ ```
+
+ ```rust
+ const FOO: bool = true
+ ```
+ "#]],
+ );
+}
+
+#[test]
fn hover_const_pat() {
check(
r#"
@@ -4203,20 +4412,20 @@ pub fn gimme() -> theitem::TheItem {
}
"#,
expect![[r#"
- *[`TheItem`]*
+ *[`TheItem`]*
- ```rust
- test::theitem
- ```
+ ```rust
+ test::theitem
+ ```
- ```rust
- pub struct TheItem
- ```
+ ```rust
+ pub struct TheItem // size = 0, align = 1
+ ```
- ---
+ ---
- This is the item. Cool!
- "#]],
+ This is the item. Cool!
+ "#]],
);
}
@@ -4351,20 +4560,20 @@ mod string {
}
"#,
expect![[r#"
- *String*
+ *String*
- ```rust
- main
- ```
+ ```rust
+ main
+ ```
- ```rust
- struct String
- ```
+ ```rust
+ struct String // size = 0, align = 1
+ ```
- ---
+ ---
- Custom `String` type.
- "#]],
+ Custom `String` type.
+ "#]],
)
}
@@ -5025,7 +5234,7 @@ foo_macro!(
```
```rust
- pub struct Foo
+ pub struct Foo // size = 0, align = 1
```
---
@@ -5040,7 +5249,7 @@ fn hover_intra_in_attr() {
check(
r#"
#[doc = "Doc comment for [`Foo$0`]"]
-pub struct Foo;
+pub struct Foo(i32);
"#,
expect![[r#"
*[`Foo`]*
@@ -5050,7 +5259,7 @@ pub struct Foo;
```
```rust
- pub struct Foo
+ pub struct Foo // size = 4, align = 4
```
---
@@ -5156,6 +5365,28 @@ enum Enum {
}
#[test]
+fn hover_record_variant_field() {
+ check(
+ r#"
+enum Enum {
+ RecordV { field$0: u32 }
+}
+"#,
+ expect![[r#"
+ *field*
+
+ ```rust
+ test::RecordV
+ ```
+
+ ```rust
+ field: u32 // size = 4, align = 4
+ ```
+ "#]],
+ );
+}
+
+#[test]
fn hover_trait_impl_assoc_item_def_doc_forwarding() {
check(
r#"
@@ -5307,3 +5538,38 @@ fn main() { $0V; }
"#]],
);
}
+
+#[test]
+fn hover_rest_pat() {
+ check(
+ r#"
+struct Struct {a: u32, b: u32, c: u8, d: u16};
+
+fn main() {
+ let Struct {a, c, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
+}
+"#,
+ expect![[r#"
+ *..*
+ ```rust
+ .., b: u32, d: u16
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+struct Struct {a: u32, b: u32, c: u8, d: u16};
+
+fn main() {
+ let Struct {a, b, c, d, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
+}
+"#,
+ expect![[r#"
+ *..*
+ ```rust
+ ..
+ ```
+ "#]],
+ );
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
index 37384c4e7..48a7bbfec 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
@@ -1,31 +1,42 @@
-use std::fmt;
+use std::{
+ fmt::{self, Write},
+ mem::take,
+};
use either::Either;
-use hir::{
- known, Adjust, AutoBorrow, Callable, HasVisibility, HirDisplay, Mutability, OverloadedDeref,
- PointerCast, Safety, Semantics, TypeInfo,
-};
-use ide_db::{
- base_db::FileRange, famous_defs::FamousDefs, syntax_helpers::node_ext::walk_ty, FxHashMap,
- RootDatabase,
-};
+use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics};
+use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
use itertools::Itertools;
-use stdx::to_lower_snake_case;
+use stdx::never;
use syntax::{
- ast::{self, AstNode, HasArgList, HasGenericParams, HasName, UnaryOp},
- match_ast, Direction, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
- TextSize, T,
+ ast::{self, AstNode},
+ match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
};
-use crate::FileId;
+use crate::{navigation_target::TryToNav, FileId};
+
+mod closing_brace;
+mod implicit_static;
+mod fn_lifetime_fn;
+mod closure_ret;
+mod adjustment;
+mod chaining;
+mod param_name;
+mod binding_mode;
+mod bind_pat;
+mod discriminant;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InlayHintsConfig {
+ pub location_links: bool,
pub render_colons: bool,
pub type_hints: bool,
+ pub discriminant_hints: DiscriminantHints,
pub parameter_hints: bool,
pub chaining_hints: bool,
pub adjustment_hints: AdjustmentHints,
+ pub adjustment_hints_mode: AdjustmentHintsMode,
+ pub adjustment_hints_hide_outside_unsafe: bool,
pub closure_return_type_hints: ClosureReturnTypeHints,
pub binding_mode_hints: bool,
pub lifetime_elision_hints: LifetimeElisionHints,
@@ -44,6 +55,13 @@ pub enum ClosureReturnTypeHints {
}
#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum DiscriminantHints {
+ Always,
+ Never,
+ Fieldless,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LifetimeElisionHints {
Always,
SkipTrivial,
@@ -57,6 +75,14 @@ pub enum AdjustmentHints {
Never,
}
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum AdjustmentHintsMode {
+ Prefix,
+ Postfix,
+ PreferPrefix,
+ PreferPostfix,
+}
+
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
BindingModeHint,
@@ -65,10 +91,13 @@ pub enum InlayKind {
ClosureReturnTypeHint,
GenericParamListHint,
AdjustmentHint,
- AdjustmentHintClosingParenthesis,
+ AdjustmentHintPostfix,
LifetimeHint,
ParameterHint,
TypeHint,
+ DiscriminantHint,
+ OpeningParenthesis,
+ ClosingParenthesis,
}
#[derive(Debug)]
@@ -86,6 +115,7 @@ pub enum InlayTooltip {
HoverOffset(FileId, TextSize),
}
+#[derive(Default)]
pub struct InlayHintLabel {
pub parts: Vec<InlayHintLabelPart>,
}
@@ -169,6 +199,101 @@ impl fmt::Debug for InlayHintLabelPart {
}
}
+#[derive(Debug)]
+struct InlayHintLabelBuilder<'a> {
+ db: &'a RootDatabase,
+ result: InlayHintLabel,
+ last_part: String,
+ location_link_enabled: bool,
+ location: Option<FileRange>,
+}
+
+impl fmt::Write for InlayHintLabelBuilder<'_> {
+ fn write_str(&mut self, s: &str) -> fmt::Result {
+ self.last_part.write_str(s)
+ }
+}
+
+impl HirWrite for InlayHintLabelBuilder<'_> {
+ fn start_location_link(&mut self, def: ModuleDefId) {
+ if !self.location_link_enabled {
+ return;
+ }
+ if self.location.is_some() {
+ never!("location link is already started");
+ }
+ self.make_new_part();
+ let Some(location) = ModuleDef::from(def).try_to_nav(self.db) else { return };
+ let location =
+ FileRange { file_id: location.file_id, range: location.focus_or_full_range() };
+ self.location = Some(location);
+ }
+
+ fn end_location_link(&mut self) {
+ if !self.location_link_enabled {
+ return;
+ }
+ self.make_new_part();
+ }
+}
+
+impl InlayHintLabelBuilder<'_> {
+ fn make_new_part(&mut self) {
+ self.result.parts.push(InlayHintLabelPart {
+ text: take(&mut self.last_part),
+ linked_location: self.location.take(),
+ });
+ }
+
+ fn finish(mut self) -> InlayHintLabel {
+ self.make_new_part();
+ self.result
+ }
+}
+
+fn label_of_ty(
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ ty: hir::Type,
+) -> Option<InlayHintLabel> {
+ fn rec(
+ sema: &Semantics<'_, RootDatabase>,
+ famous_defs: &FamousDefs<'_, '_>,
+ mut max_length: Option<usize>,
+ ty: hir::Type,
+ label_builder: &mut InlayHintLabelBuilder<'_>,
+ ) {
+ let iter_item_type = hint_iterator(sema, famous_defs, &ty);
+ match iter_item_type {
+ Some(ty) => {
+ const LABEL_START: &str = "impl Iterator<Item = ";
+ const LABEL_END: &str = ">";
+
+ max_length =
+ max_length.map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len()));
+
+ label_builder.write_str(LABEL_START).unwrap();
+ rec(sema, famous_defs, max_length, ty, label_builder);
+ label_builder.write_str(LABEL_END).unwrap();
+ }
+ None => {
+ let _ = ty.display_truncated(sema.db, max_length).write_to(label_builder);
+ }
+ };
+ }
+
+ let mut label_builder = InlayHintLabelBuilder {
+ db: sema.db,
+ last_part: String::new(),
+ location: None,
+ location_link_enabled: config.location_links,
+ result: InlayHintLabel::default(),
+ };
+ rec(sema, famous_defs, config.max_length, ty, &mut label_builder);
+ let r = label_builder.finish();
+ Some(r)
+}
+
// Feature: Inlay Hints
//
// rust-analyzer shows additional information inline with the source code.
@@ -200,7 +325,7 @@ pub(crate) fn inlay_hints(
let mut acc = Vec::new();
- if let Some(scope) = sema.scope(&file) {
+ if let Some(scope) = sema.scope(file) {
let famous_defs = FamousDefs(&sema, scope.krate());
let hints = |node| hints(&mut acc, &famous_defs, config, file_id, node);
@@ -226,18 +351,18 @@ fn hints(
file_id: FileId,
node: SyntaxNode,
) {
- closing_brace_hints(hints, sema, config, file_id, node.clone());
+ closing_brace::hints(hints, sema, config, file_id, node.clone());
match_ast! {
match node {
ast::Expr(expr) => {
- chaining_hints(hints, sema, &famous_defs, config, file_id, &expr);
- adjustment_hints(hints, sema, config, &expr);
+ chaining::hints(hints, famous_defs, config, file_id, &expr);
+ adjustment::hints(hints, sema, config, &expr);
match expr {
- ast::Expr::CallExpr(it) => param_name_hints(hints, sema, config, ast::Expr::from(it)),
+ ast::Expr::CallExpr(it) => param_name::hints(hints, sema, config, ast::Expr::from(it)),
ast::Expr::MethodCallExpr(it) => {
- param_name_hints(hints, sema, config, ast::Expr::from(it))
+ param_name::hints(hints, sema, config, ast::Expr::from(it))
}
- ast::Expr::ClosureExpr(it) => closure_ret_hints(hints, sema, &famous_defs, config, file_id, it),
+ ast::Expr::ClosureExpr(it) => closure_ret::hints(hints, famous_defs, config, file_id, it),
// We could show reborrows for all expressions, but usually that is just noise to the user
// and the main point here is to show why "moving" a mutable reference doesn't necessarily move it
// ast::Expr::PathExpr(_) => reborrow_hints(hints, sema, config, &expr),
@@ -245,21 +370,24 @@ fn hints(
}
},
ast::Pat(it) => {
- binding_mode_hints(hints, sema, config, &it);
+ binding_mode::hints(hints, sema, config, &it);
if let ast::Pat::IdentPat(it) = it {
- bind_pat_hints(hints, sema, config, file_id, &it);
+ bind_pat::hints(hints, famous_defs, config, file_id, &it);
}
Some(())
},
ast::Item(it) => match it {
// FIXME: record impl lifetimes so they aren't being reused in assoc item lifetime inlay hints
ast::Item::Impl(_) => None,
- ast::Item::Fn(it) => fn_lifetime_fn_hints(hints, config, it),
+ ast::Item::Fn(it) => fn_lifetime_fn::hints(hints, config, it),
// static type elisions
- ast::Item::Static(it) => implicit_static_hints(hints, config, Either::Left(it)),
- ast::Item::Const(it) => implicit_static_hints(hints, config, Either::Right(it)),
+ ast::Item::Static(it) => implicit_static::hints(hints, config, Either::Left(it)),
+ ast::Item::Const(it) => implicit_static::hints(hints, config, Either::Right(it)),
_ => None,
},
+ ast::Variant(v) => {
+ discriminant::hints(hints, famous_defs, config, file_id, &v)
+ },
// FIXME: fn-ptr type, dyn fn type, and trait object type elisions
ast::Type(_) => None,
_ => None,
@@ -267,733 +395,12 @@ fn hints(
};
}
-fn closing_brace_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- file_id: FileId,
- node: SyntaxNode,
-) -> Option<()> {
- let min_lines = config.closing_brace_hints_min_lines?;
-
- let name = |it: ast::Name| it.syntax().text_range();
-
- let mut closing_token;
- let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
- closing_token = item_list.r_curly_token()?;
-
- let parent = item_list.syntax().parent()?;
- match_ast! {
- match parent {
- ast::Impl(imp) => {
- let imp = sema.to_def(&imp)?;
- let ty = imp.self_ty(sema.db);
- let trait_ = imp.trait_(sema.db);
- let hint_text = match trait_ {
- Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
- None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
- };
- (hint_text, None)
- },
- ast::Trait(tr) => {
- (format!("trait {}", tr.name()?), tr.name().map(name))
- },
- _ => return None,
- }
- }
- } else if let Some(list) = ast::ItemList::cast(node.clone()) {
- closing_token = list.r_curly_token()?;
-
- let module = ast::Module::cast(list.syntax().parent()?)?;
- (format!("mod {}", module.name()?), module.name().map(name))
- } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
- closing_token = block.stmt_list()?.r_curly_token()?;
-
- let parent = block.syntax().parent()?;
- match_ast! {
- match parent {
- ast::Fn(it) => {
- // FIXME: this could include parameters, but `HirDisplay` prints too much info
- // and doesn't respect the max length either, so the hints end up way too long
- (format!("fn {}", it.name()?), it.name().map(name))
- },
- ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
- ast::Const(it) => {
- if it.underscore_token().is_some() {
- ("const _".into(), None)
- } else {
- (format!("const {}", it.name()?), it.name().map(name))
- }
- },
- _ => return None,
- }
- }
- } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
- let last_token = mac.syntax().last_token()?;
- if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
- return None;
- }
- closing_token = last_token;
-
- (
- format!("{}!", mac.path()?),
- mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range()),
- )
- } else {
- return None;
- };
-
- if let Some(mut next) = closing_token.next_token() {
- if next.kind() == T![;] {
- if let Some(tok) = next.next_token() {
- closing_token = next;
- next = tok;
- }
- }
- if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
- // Only display the hint if the `}` is the last token on the line
- return None;
- }
- }
-
- let mut lines = 1;
- node.text().for_each_chunk(|s| lines += s.matches('\n').count());
- if lines < min_lines {
- return None;
- }
-
- let linked_location = name_range.map(|range| FileRange { file_id, range });
- acc.push(InlayHint {
- range: closing_token.text_range(),
- kind: InlayKind::ClosingBraceHint,
- label: InlayHintLabel { parts: vec![InlayHintLabelPart { text: label, linked_location }] },
- tooltip: None, // provided by label part location
- });
-
- None
-}
-
-fn implicit_static_hints(
- acc: &mut Vec<InlayHint>,
- config: &InlayHintsConfig,
- statik_or_const: Either<ast::Static, ast::Const>,
-) -> Option<()> {
- if config.lifetime_elision_hints != LifetimeElisionHints::Always {
- return None;
- }
-
- if let Either::Right(it) = &statik_or_const {
- if ast::AssocItemList::can_cast(
- it.syntax().parent().map_or(SyntaxKind::EOF, |it| it.kind()),
- ) {
- return None;
- }
- }
-
- if let Some(ast::Type::RefType(ty)) = statik_or_const.either(|it| it.ty(), |it| it.ty()) {
- if ty.lifetime().is_none() {
- let t = ty.amp_token()?;
- acc.push(InlayHint {
- range: t.text_range(),
- kind: InlayKind::LifetimeHint,
- label: "'static".to_owned().into(),
- tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
- });
- }
- }
-
- Some(())
-}
-
-fn fn_lifetime_fn_hints(
- acc: &mut Vec<InlayHint>,
- config: &InlayHintsConfig,
- func: ast::Fn,
-) -> Option<()> {
- if config.lifetime_elision_hints == LifetimeElisionHints::Never {
- return None;
- }
-
- let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
- range: t.text_range(),
- kind: InlayKind::LifetimeHint,
- label: label.into(),
- tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
- };
-
- let param_list = func.param_list()?;
- let generic_param_list = func.generic_param_list();
- let ret_type = func.ret_type();
- let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
-
- let is_elided = |lt: &Option<ast::Lifetime>| match lt {
- Some(lt) => matches!(lt.text().as_str(), "'_"),
- None => true,
- };
-
- let potential_lt_refs = {
- let mut acc: Vec<_> = vec![];
- if let Some(self_param) = &self_param {
- let lifetime = self_param.lifetime();
- let is_elided = is_elided(&lifetime);
- acc.push((None, self_param.amp_token(), lifetime, is_elided));
- }
- param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
- // FIXME: check path types
- walk_ty(&ty, &mut |ty| match ty {
- ast::Type::RefType(r) => {
- let lifetime = r.lifetime();
- let is_elided = is_elided(&lifetime);
- acc.push((
- pat.as_ref().and_then(|it| match it {
- ast::Pat::IdentPat(p) => p.name(),
- _ => None,
- }),
- r.amp_token(),
- lifetime,
- is_elided,
- ))
- }
- _ => (),
- })
- });
- acc
- };
-
- // allocate names
- let mut gen_idx_name = {
- let mut gen = (0u8..).map(|idx| match idx {
- idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
- idx => format!("'{idx}").into(),
- });
- move || gen.next().unwrap_or_default()
- };
- let mut allocated_lifetimes = vec![];
-
- let mut used_names: FxHashMap<SmolStr, usize> =
- match config.param_names_for_lifetime_elision_hints {
- true => generic_param_list
- .iter()
- .flat_map(|gpl| gpl.lifetime_params())
- .filter_map(|param| param.lifetime())
- .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
- .collect(),
- false => Default::default(),
- };
- {
- let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
- if let Some(_) = &self_param {
- if let Some(_) = potential_lt_refs.next() {
- allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
- // self can't be used as a lifetime, so no need to check for collisions
- "'self".into()
- } else {
- gen_idx_name()
- });
- }
- }
- potential_lt_refs.for_each(|(name, ..)| {
- let name = match name {
- Some(it) if config.param_names_for_lifetime_elision_hints => {
- if let Some(c) = used_names.get_mut(it.text().as_str()) {
- *c += 1;
- SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
- } else {
- used_names.insert(it.text().as_str().into(), 0);
- SmolStr::from_iter(["\'", it.text().as_str()])
- }
- }
- _ => gen_idx_name(),
- };
- allocated_lifetimes.push(name);
- });
- }
-
- // fetch output lifetime if elision rule applies
- let output = match potential_lt_refs.as_slice() {
- [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
- match lifetime {
- Some(lt) => match lt.text().as_str() {
- "'_" => allocated_lifetimes.get(0).cloned(),
- "'static" => None,
- name => Some(name.into()),
- },
- None => allocated_lifetimes.get(0).cloned(),
- }
- }
- [..] => None,
- };
-
- if allocated_lifetimes.is_empty() && output.is_none() {
- return None;
- }
-
- // apply hints
- // apply output if required
- let mut is_trivial = true;
- if let (Some(output_lt), Some(r)) = (&output, ret_type) {
- if let Some(ty) = r.ty() {
- walk_ty(&ty, &mut |ty| match ty {
- ast::Type::RefType(ty) if ty.lifetime().is_none() => {
- if let Some(amp) = ty.amp_token() {
- is_trivial = false;
- acc.push(mk_lt_hint(amp, output_lt.to_string()));
- }
- }
- _ => (),
- })
- }
- }
-
- if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
- return None;
- }
-
- let mut a = allocated_lifetimes.iter();
- for (_, amp_token, _, is_elided) in potential_lt_refs {
- if is_elided {
- let t = amp_token?;
- let lt = a.next()?;
- acc.push(mk_lt_hint(t, lt.to_string()));
- }
- }
-
- // generate generic param list things
- match (generic_param_list, allocated_lifetimes.as_slice()) {
- (_, []) => (),
- (Some(gpl), allocated_lifetimes) => {
- let angle_tok = gpl.l_angle_token()?;
- let is_empty = gpl.generic_params().next().is_none();
- acc.push(InlayHint {
- range: angle_tok.text_range(),
- kind: InlayKind::LifetimeHint,
- label: format!(
- "{}{}",
- allocated_lifetimes.iter().format(", "),
- if is_empty { "" } else { ", " }
- )
- .into(),
- tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
- });
- }
- (None, allocated_lifetimes) => acc.push(InlayHint {
- range: func.name()?.syntax().text_range(),
- kind: InlayKind::GenericParamListHint,
- label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
- tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
- }),
- }
- Some(())
-}
-
-fn closure_ret_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- famous_defs: &FamousDefs<'_, '_>,
- config: &InlayHintsConfig,
- file_id: FileId,
- closure: ast::ClosureExpr,
-) -> Option<()> {
- if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
- return None;
- }
-
- if closure.ret_type().is_some() {
- return None;
- }
-
- if !closure_has_block_body(&closure)
- && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
- {
- return None;
- }
-
- let param_list = closure.param_list()?;
-
- let closure = sema.descend_node_into_attributes(closure.clone()).pop()?;
- let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
- let callable = ty.as_callable(sema.db)?;
- let ty = callable.return_type();
- if ty.is_unit() {
- return None;
- }
- acc.push(InlayHint {
- range: param_list.syntax().text_range(),
- kind: InlayKind::ClosureReturnTypeHint,
- label: hint_iterator(sema, &famous_defs, config, &ty)
- .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
- .into(),
- tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
- });
- Some(())
-}
-
-fn adjustment_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- expr: &ast::Expr,
-) -> Option<()> {
- if config.adjustment_hints == AdjustmentHints::Never {
- return None;
- }
-
- if let ast::Expr::ParenExpr(_) = expr {
- // These inherit from the inner expression which would result in duplicate hints
- return None;
- }
-
- let parent = expr.syntax().parent().and_then(ast::Expr::cast);
- let descended = sema.descend_node_into_attributes(expr.clone()).pop();
- let desc_expr = descended.as_ref().unwrap_or(expr);
- let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
- let needs_parens = match parent {
- Some(parent) => {
- match parent {
- ast::Expr::AwaitExpr(_)
- | ast::Expr::CallExpr(_)
- | ast::Expr::CastExpr(_)
- | ast::Expr::FieldExpr(_)
- | ast::Expr::MethodCallExpr(_)
- | ast::Expr::TryExpr(_) => true,
- // FIXME: shorthands need special casing, though not sure if adjustments are even valid there
- ast::Expr::RecordExpr(_) => false,
- ast::Expr::IndexExpr(index) => index.base().as_ref() == Some(expr),
- _ => false,
- }
- }
- None => false,
- };
- if needs_parens {
- acc.push(InlayHint {
- range: expr.syntax().text_range(),
- kind: InlayKind::AdjustmentHint,
- label: "(".into(),
- tooltip: None,
- });
- }
- for adjustment in adjustments.into_iter().rev() {
- // FIXME: Add some nicer tooltips to each of these
- let text = match adjustment {
- Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
- "<never-to-any>"
- }
- Adjust::Deref(None) => "*",
- Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => "*",
- Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => "*",
- Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => "&",
- Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => "&mut ",
- Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => "&raw const ",
- Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => "&raw mut ",
- // some of these could be represented via `as` casts, but that's not too nice and
- // handling everything as a prefix expr makes the `(` and `)` insertion easier
- Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
- match cast {
- PointerCast::ReifyFnPointer => "<fn-item-to-fn-pointer>",
- PointerCast::UnsafeFnPointer => "<safe-fn-pointer-to-unsafe-fn-pointer>",
- PointerCast::ClosureFnPointer(Safety::Unsafe) => {
- "<closure-to-unsafe-fn-pointer>"
- }
- PointerCast::ClosureFnPointer(Safety::Safe) => "<closure-to-fn-pointer>",
- PointerCast::MutToConstPointer => "<mut-ptr-to-const-ptr>",
- PointerCast::ArrayToPointer => "<array-ptr-to-element-ptr>",
- PointerCast::Unsize => "<unsize>",
- }
- }
- _ => continue,
- };
- acc.push(InlayHint {
- range: expr.syntax().text_range(),
- kind: InlayKind::AdjustmentHint,
- label: text.into(),
- tooltip: None,
- });
- }
- if needs_parens {
- acc.push(InlayHint {
- range: expr.syntax().text_range(),
- kind: InlayKind::AdjustmentHintClosingParenthesis,
- label: ")".into(),
- tooltip: None,
- });
- }
- Some(())
-}
-
-fn chaining_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- famous_defs: &FamousDefs<'_, '_>,
- config: &InlayHintsConfig,
- file_id: FileId,
- expr: &ast::Expr,
-) -> Option<()> {
- if !config.chaining_hints {
- return None;
- }
-
- if matches!(expr, ast::Expr::RecordExpr(_)) {
- return None;
- }
-
- let descended = sema.descend_node_into_attributes(expr.clone()).pop();
- let desc_expr = descended.as_ref().unwrap_or(expr);
-
- let mut tokens = expr
- .syntax()
- .siblings_with_tokens(Direction::Next)
- .filter_map(NodeOrToken::into_token)
- .filter(|t| match t.kind() {
- SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
- SyntaxKind::COMMENT => false,
- _ => true,
- });
-
- // Chaining can be defined as an expression whose next sibling tokens are newline and dot
- // Ignoring extra whitespace and comments
- let next = tokens.next()?.kind();
- if next == SyntaxKind::WHITESPACE {
- let mut next_next = tokens.next()?.kind();
- while next_next == SyntaxKind::WHITESPACE {
- next_next = tokens.next()?.kind();
- }
- if next_next == T![.] {
- let ty = sema.type_of_expr(desc_expr)?.original;
- if ty.is_unknown() {
- return None;
- }
- if matches!(expr, ast::Expr::PathExpr(_)) {
- if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
- if st.fields(sema.db).is_empty() {
- return None;
- }
- }
- }
- acc.push(InlayHint {
- range: expr.syntax().text_range(),
- kind: InlayKind::ChainingHint,
- label: hint_iterator(sema, &famous_defs, config, &ty)
- .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string())
- .into(),
- tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
- });
- }
- }
- Some(())
-}
-
-fn param_name_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- expr: ast::Expr,
-) -> Option<()> {
- if !config.parameter_hints {
- return None;
- }
-
- let (callable, arg_list) = get_callable(sema, &expr)?;
- let hints = callable
- .params(sema.db)
- .into_iter()
- .zip(arg_list.args())
- .filter_map(|((param, _ty), arg)| {
- // Only annotate hints for expressions that exist in the original file
- let range = sema.original_range_opt(arg.syntax())?;
- let (param_name, name_syntax) = match param.as_ref()? {
- Either::Left(pat) => ("self".to_string(), pat.name()),
- Either::Right(pat) => match pat {
- ast::Pat::IdentPat(it) => (it.name()?.to_string(), it.name()),
- _ => return None,
- },
- };
- Some((name_syntax, param_name, arg, range))
- })
- .filter(|(_, param_name, arg, _)| {
- !should_hide_param_name_hint(sema, &callable, param_name, arg)
- })
- .map(|(param, param_name, _, FileRange { range, .. })| {
- let mut tooltip = None;
- if let Some(name) = param {
- if let hir::CallableKind::Function(f) = callable.kind() {
- // assert the file is cached so we can map out of macros
- if let Some(_) = sema.source(f) {
- tooltip = sema.original_range_opt(name.syntax());
- }
- }
- }
-
- InlayHint {
- range,
- kind: InlayKind::ParameterHint,
- label: param_name.into(),
- tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())),
- }
- });
-
- acc.extend(hints);
- Some(())
-}
-
-fn binding_mode_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- pat: &ast::Pat,
-) -> Option<()> {
- if !config.binding_mode_hints {
- return None;
- }
-
- let range = pat.syntax().text_range();
- sema.pattern_adjustments(&pat).iter().for_each(|ty| {
- let reference = ty.is_reference();
- let mut_reference = ty.is_mutable_reference();
- let r = match (reference, mut_reference) {
- (true, true) => "&mut",
- (true, false) => "&",
- _ => return,
- };
- acc.push(InlayHint {
- range,
- kind: InlayKind::BindingModeHint,
- label: r.to_string().into(),
- tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
- });
- });
- match pat {
- ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
- let bm = sema.binding_mode_of_pat(pat)?;
- let bm = match bm {
- hir::BindingMode::Move => return None,
- hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
- hir::BindingMode::Ref(Mutability::Shared) => "ref",
- };
- acc.push(InlayHint {
- range,
- kind: InlayKind::BindingModeHint,
- label: bm.to_string().into(),
- tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
- });
- }
- _ => (),
- }
-
- Some(())
-}
-
-fn bind_pat_hints(
- acc: &mut Vec<InlayHint>,
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- file_id: FileId,
- pat: &ast::IdentPat,
-) -> Option<()> {
- if !config.type_hints {
- return None;
- }
-
- let descended = sema.descend_node_into_attributes(pat.clone()).pop();
- let desc_pat = descended.as_ref().unwrap_or(pat);
- let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
-
- if should_not_display_type_hint(sema, config, pat, &ty) {
- return None;
- }
-
- let krate = sema.scope(desc_pat.syntax())?.krate();
- let famous_defs = FamousDefs(sema, krate);
- let label = hint_iterator(sema, &famous_defs, config, &ty);
-
- let label = match label {
- Some(label) => label,
- None => {
- let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
- if config.hide_named_constructor_hints
- && is_named_constructor(sema, pat, &ty_name).is_some()
- {
- return None;
- }
- ty_name
- }
- };
-
- acc.push(InlayHint {
- range: match pat.name() {
- Some(name) => name.syntax().text_range(),
- None => pat.syntax().text_range(),
- },
- kind: InlayKind::TypeHint,
- label: label.into(),
- tooltip: pat
- .name()
- .map(|it| it.syntax().text_range())
- .map(|it| InlayTooltip::HoverRanged(file_id, it)),
- });
-
- Some(())
-}
-
-fn is_named_constructor(
- sema: &Semantics<'_, RootDatabase>,
- pat: &ast::IdentPat,
- ty_name: &str,
-) -> Option<()> {
- let let_node = pat.syntax().parent()?;
- let expr = match_ast! {
- match let_node {
- ast::LetStmt(it) => it.initializer(),
- ast::LetExpr(it) => it.expr(),
- _ => None,
- }
- }?;
-
- let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
- // unwrap postfix expressions
- let expr = match expr {
- ast::Expr::TryExpr(it) => it.expr(),
- ast::Expr::AwaitExpr(it) => it.expr(),
- expr => Some(expr),
- }?;
- let expr = match expr {
- ast::Expr::CallExpr(call) => match call.expr()? {
- ast::Expr::PathExpr(path) => path,
- _ => return None,
- },
- ast::Expr::PathExpr(path) => path,
- _ => return None,
- };
- let path = expr.path()?;
-
- let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
- let callable_kind = callable.map(|it| it.kind());
- let qual_seg = match callable_kind {
- Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
- path.qualifier()?.segment()
- }
- _ => path.segment(),
- }?;
-
- let ctor_name = match qual_seg.kind()? {
- ast::PathSegmentKind::Name(name_ref) => {
- match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
- Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
- None => name_ref.to_string(),
- }
- }
- ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
- _ => return None,
- };
- (ctor_name == ty_name).then(|| ())
-}
-
-/// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
+/// Checks if the type is an Iterator from std::iter and returns its item type.
fn hint_iterator(
sema: &Semantics<'_, RootDatabase>,
famous_defs: &FamousDefs<'_, '_>,
- config: &InlayHintsConfig,
ty: &hir::Type,
-) -> Option<String> {
+) -> Option<hir::Type> {
let db = sema.db;
let strukt = ty.strip_references().as_adt()?;
let krate = strukt.module(db).krate();
@@ -1016,289 +423,32 @@ fn hint_iterator(
_ => None,
})?;
if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
- const LABEL_START: &str = "impl Iterator<Item = ";
- const LABEL_END: &str = ">";
-
- let ty_display = hint_iterator(sema, famous_defs, config, &ty)
- .map(|assoc_type_impl| assoc_type_impl.to_string())
- .unwrap_or_else(|| {
- ty.display_truncated(
- db,
- config
- .max_length
- .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
- )
- .to_string()
- });
- return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END));
+ return Some(ty);
}
}
None
}
-fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
- if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
- let pat_text = bind_pat.to_string();
- enum_data
- .variants(db)
- .into_iter()
- .map(|variant| variant.name(db).to_smol_str())
- .any(|enum_name| enum_name == pat_text)
- } else {
- false
- }
-}
-
-fn should_not_display_type_hint(
- sema: &Semantics<'_, RootDatabase>,
- config: &InlayHintsConfig,
- bind_pat: &ast::IdentPat,
- pat_ty: &hir::Type,
-) -> bool {
- let db = sema.db;
-
- if pat_ty.is_unknown() {
- return true;
- }
-
- if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
- if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
- return true;
- }
- }
-
- if config.hide_closure_initialization_hints {
- if let Some(parent) = bind_pat.syntax().parent() {
- if let Some(it) = ast::LetStmt::cast(parent.clone()) {
- if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
- if closure_has_block_body(&closure) {
- return true;
- }
- }
- }
- }
- }
-
- for node in bind_pat.syntax().ancestors() {
- match_ast! {
- match node {
- ast::LetStmt(it) => return it.ty().is_some(),
- // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
- ast::Param(it) => return it.ty().is_some(),
- ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
- ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
- ast::IfExpr(_) => return false,
- ast::WhileExpr(_) => return false,
- ast::ForExpr(it) => {
- // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
- // Type of expr should be iterable.
- return it.in_token().is_none() ||
- it.iterable()
- .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
- .map(TypeInfo::original)
- .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
- },
- _ => (),
- }
- }
- }
- false
-}
-
fn closure_has_block_body(closure: &ast::ClosureExpr) -> bool {
matches!(closure.body(), Some(ast::Expr::BlockExpr(_)))
}
-fn should_hide_param_name_hint(
- sema: &Semantics<'_, RootDatabase>,
- callable: &hir::Callable,
- param_name: &str,
- argument: &ast::Expr,
-) -> bool {
- // These are to be tested in the `parameter_hint_heuristics` test
- // hide when:
- // - the parameter name is a suffix of the function's name
- // - the argument is a qualified constructing or call expression where the qualifier is an ADT
- // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
- // of argument with _ splitting it off
- // - param starts with `ra_fixture`
- // - param is a well known name in a unary function
-
- let param_name = param_name.trim_start_matches('_');
- if param_name.is_empty() {
- return true;
- }
-
- if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
- return false;
- }
-
- let fn_name = match callable.kind() {
- hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
- _ => None,
- };
- let fn_name = fn_name.as_deref();
- is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
- || is_argument_similar_to_param_name(argument, param_name)
- || param_name.starts_with("ra_fixture")
- || (callable.n_params() == 1 && is_obvious_param(param_name))
- || is_adt_constructor_similar_to_param_name(sema, argument, param_name)
-}
-
-fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
- // check whether param_name and argument are the same or
- // whether param_name is a prefix/suffix of argument(split at `_`)
- let argument = match get_string_representation(argument) {
- Some(argument) => argument,
- None => return false,
- };
-
- // std is honestly too panic happy...
- let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
-
- let param_name = param_name.trim_start_matches('_');
- let argument = argument.trim_start_matches('_');
-
- match str_split_at(argument, param_name.len()) {
- Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
- return rest.is_empty() || rest.starts_with('_');
- }
- _ => (),
- }
- match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
- Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
- return rest.is_empty() || rest.ends_with('_');
- }
- _ => (),
- }
- false
-}
-
-/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
-///
-/// `fn strip_suffix(suffix)` will be hidden.
-/// `fn stripsuffix(suffix)` will not be hidden.
-fn is_param_name_suffix_of_fn_name(
- param_name: &str,
- callable: &Callable,
- fn_name: Option<&str>,
-) -> bool {
- match (callable.n_params(), fn_name) {
- (1, Some(function)) => {
- function == param_name
- || function
- .len()
- .checked_sub(param_name.len())
- .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
- .map_or(false, |(prefix, suffix)| {
- suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
- })
- }
- _ => false,
- }
-}
-
-fn is_adt_constructor_similar_to_param_name(
- sema: &Semantics<'_, RootDatabase>,
- argument: &ast::Expr,
- param_name: &str,
-) -> bool {
- let path = match argument {
- ast::Expr::CallExpr(c) => c.expr().and_then(|e| match e {
- ast::Expr::PathExpr(p) => p.path(),
- _ => None,
- }),
- ast::Expr::PathExpr(p) => p.path(),
- ast::Expr::RecordExpr(r) => r.path(),
- _ => return false,
- };
- let path = match path {
- Some(it) => it,
- None => return false,
- };
- (|| match sema.resolve_path(&path)? {
- hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
- Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
- }
- hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_)) => {
- if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
- return Some(true);
- }
- let qual = path.qualifier()?;
- match sema.resolve_path(&qual)? {
- hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
- Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
- }
- _ => None,
- }
- }
- _ => None,
- })()
- .unwrap_or(false)
-}
-
-fn get_string_representation(expr: &ast::Expr) -> Option<String> {
- match expr {
- ast::Expr::MethodCallExpr(method_call_expr) => {
- let name_ref = method_call_expr.name_ref()?;
- match name_ref.text().as_str() {
- "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
- name_ref => Some(name_ref.to_owned()),
- }
- }
- ast::Expr::MacroExpr(macro_expr) => {
- Some(macro_expr.macro_call()?.path()?.segment()?.to_string())
- }
- ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
- ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
- ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
- ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
- ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
- _ => None,
- }
-}
-
-fn is_obvious_param(param_name: &str) -> bool {
- // avoid displaying hints for common functions like map, filter, etc.
- // or other obvious words used in std
- let is_obvious_param_name =
- matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
- param_name.len() == 1 || is_obvious_param_name
-}
-
-fn get_callable(
- sema: &Semantics<'_, RootDatabase>,
- expr: &ast::Expr,
-) -> Option<(hir::Callable, ast::ArgList)> {
- match expr {
- ast::Expr::CallExpr(expr) => {
- let descended = sema.descend_node_into_attributes(expr.clone()).pop();
- let expr = descended.as_ref().unwrap_or(expr);
- sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
- }
- ast::Expr::MethodCallExpr(expr) => {
- let descended = sema.descend_node_into_attributes(expr.clone()).pop();
- let expr = descended.as_ref().unwrap_or(expr);
- sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
- }
- _ => None,
- }
-}
-
#[cfg(test)]
mod tests {
- use expect_test::{expect, Expect};
+ use expect_test::Expect;
use itertools::Itertools;
- use syntax::{TextRange, TextSize};
use test_utils::extract_annotations;
- use crate::inlay_hints::AdjustmentHints;
+ use crate::inlay_hints::{AdjustmentHints, AdjustmentHintsMode};
+ use crate::DiscriminantHints;
use crate::{fixture, inlay_hints::InlayHintsConfig, LifetimeElisionHints};
use super::ClosureReturnTypeHints;
- const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ location_links: false,
+ discriminant_hints: DiscriminantHints::Never,
render_colons: false,
type_hints: false,
parameter_hints: false,
@@ -1306,6 +456,8 @@ mod tests {
lifetime_elision_hints: LifetimeElisionHints::Never,
closure_return_type_hints: ClosureReturnTypeHints::Never,
adjustment_hints: AdjustmentHints::Never,
+ adjustment_hints_mode: AdjustmentHintsMode::Prefix,
+ adjustment_hints_hide_outside_unsafe: false,
binding_mode_hints: false,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
@@ -1313,43 +465,27 @@ mod tests {
max_length: None,
closing_brace_hints_min_lines: None,
};
- const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
+ pub(super) const DISABLED_CONFIG_WITH_LINKS: InlayHintsConfig =
+ InlayHintsConfig { location_links: true, ..DISABLED_CONFIG };
+ pub(super) const TEST_CONFIG: InlayHintsConfig = InlayHintsConfig {
type_hints: true,
parameter_hints: true,
chaining_hints: true,
closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
- ..DISABLED_CONFIG
+ ..DISABLED_CONFIG_WITH_LINKS
};
#[track_caller]
- fn check(ra_fixture: &str) {
+ pub(super) fn check(ra_fixture: &str) {
check_with_config(TEST_CONFIG, ra_fixture);
}
#[track_caller]
- fn check_params(ra_fixture: &str) {
- check_with_config(
- InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
- ra_fixture,
- );
- }
-
- #[track_caller]
- fn check_types(ra_fixture: &str) {
- check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
- }
-
- #[track_caller]
- fn check_chains(ra_fixture: &str) {
- check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
- }
-
- #[track_caller]
- fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
+ pub(super) fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
let (analysis, file_id) = fixture::file(ra_fixture);
- let mut expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
+ let mut expected = extract_annotations(&analysis.file_text(file_id).unwrap());
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
let actual = inlay_hints
.into_iter()
@@ -1358,11 +494,11 @@ mod tests {
.collect::<Vec<_>>();
expected.sort_by_key(|(range, _)| range.start());
- assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
+ assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
}
#[track_caller]
- fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
+ pub(super) fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
let (analysis, file_id) = fixture::file(ra_fixture);
let inlay_hints = analysis.inlay_hints(&config, file_id, None).unwrap();
expect.assert_debug_eq(&inlay_hints)
@@ -1379,1720 +515,4 @@ fn main() {
}"#,
);
}
-
- // Parameter hint tests
-
- #[test]
- fn param_hints_only() {
- check_params(
- r#"
-fn foo(a: i32, b: i32) -> i32 { a + b }
-fn main() {
- let _x = foo(
- 4,
- //^ a
- 4,
- //^ b
- );
-}"#,
- );
- }
-
- #[test]
- fn param_hints_on_closure() {
- check_params(
- r#"
-fn main() {
- let clo = |a: u8, b: u8| a + b;
- clo(
- 1,
- //^ a
- 2,
- //^ b
- );
-}
- "#,
- );
- }
-
- #[test]
- fn param_name_similar_to_fn_name_still_hints() {
- check_params(
- r#"
-fn max(x: i32, y: i32) -> i32 { x + y }
-fn main() {
- let _x = max(
- 4,
- //^ x
- 4,
- //^ y
- );
-}"#,
- );
- }
-
- #[test]
- fn param_name_similar_to_fn_name() {
- check_params(
- r#"
-fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
-fn main() {
- let _x = param_with_underscore(
- 4,
- );
-}"#,
- );
- check_params(
- r#"
-fn param_with_underscore(underscore: i32) -> i32 { underscore }
-fn main() {
- let _x = param_with_underscore(
- 4,
- );
-}"#,
- );
- }
-
- #[test]
- fn param_name_same_as_fn_name() {
- check_params(
- r#"
-fn foo(foo: i32) -> i32 { foo }
-fn main() {
- let _x = foo(
- 4,
- );
-}"#,
- );
- }
-
- #[test]
- fn never_hide_param_when_multiple_params() {
- check_params(
- r#"
-fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
-fn main() {
- let _x = foo(
- 4,
- //^ foo
- 8,
- //^ bar
- );
-}"#,
- );
- }
-
- #[test]
- fn param_hints_look_through_as_ref_and_clone() {
- check_params(
- r#"
-fn foo(bar: i32, baz: f32) {}
-
-fn main() {
- let bar = 3;
- let baz = &"baz";
- let fez = 1.0;
- foo(bar.clone(), bar.clone());
- //^^^^^^^^^^^ baz
- foo(bar.as_ref(), bar.as_ref());
- //^^^^^^^^^^^^ baz
-}
-"#,
- );
- }
-
- #[test]
- fn self_param_hints() {
- check_params(
- r#"
-struct Foo;
-
-impl Foo {
- fn foo(self: Self) {}
- fn bar(self: &Self) {}
-}
-
-fn main() {
- Foo::foo(Foo);
- //^^^ self
- Foo::bar(&Foo);
- //^^^^ self
-}
-"#,
- )
- }
-
- #[test]
- fn param_name_hints_show_for_literals() {
- check_params(
- r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
-fn main() {
- test(
- 0xa_b,
- //^^^^^ a
- 0xa_b,
- //^^^^^ b
- );
-}"#,
- )
- }
-
- #[test]
- fn function_call_parameter_hint() {
- check_params(
- r#"
-//- minicore: option
-struct FileId {}
-struct SmolStr {}
-
-struct TextRange {}
-struct SyntaxKind {}
-struct NavigationTarget {}
-
-struct Test {}
-
-impl Test {
- fn method(&self, mut param: i32) -> i32 { param * 2 }
-
- fn from_syntax(
- file_id: FileId,
- name: SmolStr,
- focus_range: Option<TextRange>,
- full_range: TextRange,
- kind: SyntaxKind,
- docs: Option<String>,
- ) -> NavigationTarget {
- NavigationTarget {}
- }
-}
-
-fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
- foo + bar
-}
-
-fn main() {
- let not_literal = 1;
- let _: i32 = test_func(1, 2, "hello", 3, not_literal);
- //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
- let t: Test = Test {};
- t.method(123);
- //^^^ param
- Test::method(&t, 3456);
- //^^ self ^^^^ param
- Test::from_syntax(
- FileId {},
- "impl".into(),
- //^^^^^^^^^^^^^ name
- None,
- //^^^^ focus_range
- TextRange {},
- //^^^^^^^^^^^^ full_range
- SyntaxKind {},
- //^^^^^^^^^^^^^ kind
- None,
- //^^^^ docs
- );
-}"#,
- );
- }
-
- #[test]
- fn parameter_hint_heuristics() {
- check_params(
- r#"
-fn check(ra_fixture_thing: &str) {}
-
-fn map(f: i32) {}
-fn filter(predicate: i32) {}
-
-fn strip_suffix(suffix: &str) {}
-fn stripsuffix(suffix: &str) {}
-fn same(same: u32) {}
-fn same2(_same2: u32) {}
-
-fn enum_matches_param_name(completion_kind: CompletionKind) {}
-
-fn foo(param: u32) {}
-fn bar(param_eter: u32) {}
-
-enum CompletionKind {
- Keyword,
-}
-
-fn non_ident_pat((a, b): (u32, u32)) {}
-
-fn main() {
- const PARAM: u32 = 0;
- foo(PARAM);
- foo(!PARAM);
- // ^^^^^^ param
- check("");
-
- map(0);
- filter(0);
-
- strip_suffix("");
- stripsuffix("");
- //^^ suffix
- same(0);
- same2(0);
-
- enum_matches_param_name(CompletionKind::Keyword);
-
- let param = 0;
- foo(param);
- foo(param as _);
- let param_end = 0;
- foo(param_end);
- let start_param = 0;
- foo(start_param);
- let param2 = 0;
- foo(param2);
- //^^^^^^ param
-
- macro_rules! param {
- () => {};
- };
- foo(param!());
-
- let param_eter = 0;
- bar(param_eter);
- let param_eter_end = 0;
- bar(param_eter_end);
- let start_param_eter = 0;
- bar(start_param_eter);
- let param_eter2 = 0;
- bar(param_eter2);
- //^^^^^^^^^^^ param_eter
-
- non_ident_pat((0, 0));
-}"#,
- );
- }
-
- // Type-Hint tests
-
- #[test]
- fn type_hints_only() {
- check_types(
- r#"
-fn foo(a: i32, b: i32) -> i32 { a + b }
-fn main() {
- let _x = foo(4, 4);
- //^^ i32
-}"#,
- );
- }
-
- #[test]
- fn type_hints_bindings_after_at() {
- check_types(
- r#"
-//- minicore: option
-fn main() {
- let ref foo @ bar @ ref mut baz = 0;
- //^^^ &i32
- //^^^ i32
- //^^^ &mut i32
- let [x @ ..] = [0];
- //^ [i32; 1]
- if let x @ Some(_) = Some(0) {}
- //^ Option<i32>
- let foo @ (bar, baz) = (3, 3);
- //^^^ (i32, i32)
- //^^^ i32
- //^^^ i32
-}"#,
- );
- }
-
- #[test]
- fn default_generic_types_should_not_be_displayed() {
- check(
- r#"
-struct Test<K, T = u8> { k: K, t: T }
-
-fn main() {
- let zz = Test { t: 23u8, k: 33 };
- //^^ Test<i32>
- let zz_ref = &zz;
- //^^^^^^ &Test<i32>
- let test = || zz;
- //^^^^ || -> Test<i32>
-}"#,
- );
- }
-
- #[test]
- fn shorten_iterators_in_associated_params() {
- check_types(
- r#"
-//- minicore: iterators
-use core::iter;
-
-pub struct SomeIter<T> {}
-
-impl<T> SomeIter<T> {
- pub fn new() -> Self { SomeIter {} }
- pub fn push(&mut self, t: T) {}
-}
-
-impl<T> Iterator for SomeIter<T> {
- type Item = T;
- fn next(&mut self) -> Option<Self::Item> {
- None
- }
-}
-
-fn main() {
- let mut some_iter = SomeIter::new();
- //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
- some_iter.push(iter::repeat(2).take(2));
- let iter_of_iters = some_iter.take(2);
- //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
-}
-"#,
- );
- }
-
- #[test]
- fn iterator_hint_regression_issue_12674() {
- // Ensure we don't crash while solving the projection type of iterators.
- check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
- r#"
-//- minicore: iterators
-struct S<T>(T);
-impl<T> S<T> {
- fn iter(&self) -> Iter<'_, T> { loop {} }
-}
-struct Iter<'a, T: 'a>(&'a T);
-impl<'a, T> Iterator for Iter<'a, T> {
- type Item = &'a T;
- fn next(&mut self) -> Option<Self::Item> { loop {} }
-}
-struct Container<'a> {
- elements: S<&'a str>,
-}
-struct SliceIter<'a, T>(&'a T);
-impl<'a, T> Iterator for SliceIter<'a, T> {
- type Item = &'a T;
- fn next(&mut self) -> Option<Self::Item> { loop {} }
-}
-
-fn main(a: SliceIter<'_, Container>) {
- a
- .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v))))
- .map(|e| e);
-}
- "#,
- expect![[r#"
- [
- InlayHint {
- range: 484..554,
- kind: ChainingHint,
- label: [
- "impl Iterator<Item = impl Iterator<Item = &&str>>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 484..554,
- ),
- ),
- },
- InlayHint {
- range: 484..485,
- kind: ChainingHint,
- label: [
- "SliceIter<Container>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 484..485,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn infer_call_method_return_associated_types_with_generic() {
- check_types(
- r#"
- pub trait Default {
- fn default() -> Self;
- }
- pub trait Foo {
- type Bar: Default;
- }
-
- pub fn quux<T: Foo>() -> T::Bar {
- let y = Default::default();
- //^ <T as Foo>::Bar
-
- y
- }
- "#,
- );
- }
-
- #[test]
- fn fn_hints() {
- check_types(
- r#"
-//- minicore: fn, sized
-fn foo() -> impl Fn() { loop {} }
-fn foo1() -> impl Fn(f64) { loop {} }
-fn foo2() -> impl Fn(f64, f64) { loop {} }
-fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
-fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
-fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
-fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
-fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
-
-fn main() {
- let foo = foo();
- // ^^^ impl Fn()
- let foo = foo1();
- // ^^^ impl Fn(f64)
- let foo = foo2();
- // ^^^ impl Fn(f64, f64)
- let foo = foo3();
- // ^^^ impl Fn(f64, f64) -> u32
- let foo = foo4();
- // ^^^ &dyn Fn(f64, f64) -> u32
- let foo = foo5();
- // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
- let foo = foo6();
- // ^^^ impl Fn(f64, f64) -> u32
- let foo = foo7();
- // ^^^ *const impl Fn(f64, f64) -> u32
-}
-"#,
- )
- }
-
- #[test]
- fn check_hint_range_limit() {
- let fixture = r#"
- //- minicore: fn, sized
- fn foo() -> impl Fn() { loop {} }
- fn foo1() -> impl Fn(f64) { loop {} }
- fn foo2() -> impl Fn(f64, f64) { loop {} }
- fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
- fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
- fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
- fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
- fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
-
- fn main() {
- let foo = foo();
- let foo = foo1();
- let foo = foo2();
- // ^^^ impl Fn(f64, f64)
- let foo = foo3();
- // ^^^ impl Fn(f64, f64) -> u32
- let foo = foo4();
- let foo = foo5();
- let foo = foo6();
- let foo = foo7();
- }
- "#;
- let (analysis, file_id) = fixture::file(fixture);
- let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
- let inlay_hints = analysis
- .inlay_hints(
- &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
- file_id,
- Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
- )
- .unwrap();
- let actual =
- inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
- assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
- }
-
- #[test]
- fn fn_hints_ptr_rpit_fn_parentheses() {
- check_types(
- r#"
-//- minicore: fn, sized
-trait Trait {}
-
-fn foo1() -> *const impl Fn() { loop {} }
-fn foo2() -> *const (impl Fn() + Sized) { loop {} }
-fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
-fn foo4() -> *const (impl Sized + Fn()) { loop {} }
-fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
-fn foo6() -> *const (impl Fn() + Trait) { loop {} }
-fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
-fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
-fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
-fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
-
-fn main() {
- let foo = foo1();
- // ^^^ *const impl Fn()
- let foo = foo2();
- // ^^^ *const impl Fn()
- let foo = foo3();
- // ^^^ *const (impl Fn() + ?Sized)
- let foo = foo4();
- // ^^^ *const impl Fn()
- let foo = foo5();
- // ^^^ *const (impl Fn() + ?Sized)
- let foo = foo6();
- // ^^^ *const (impl Fn() + Trait)
- let foo = foo7();
- // ^^^ *const (impl Fn() + Trait)
- let foo = foo8();
- // ^^^ *const (impl Fn() + Trait + ?Sized)
- let foo = foo9();
- // ^^^ *const (impl Fn() -> u8 + ?Sized)
- let foo = foo10();
- // ^^^ *const impl Fn()
-}
-"#,
- )
- }
-
- #[test]
- fn unit_structs_have_no_type_hints() {
- check_types(
- r#"
-//- minicore: result
-struct SyntheticSyntax;
-
-fn main() {
- match Ok(()) {
- Ok(_) => (),
- Err(SyntheticSyntax) => (),
- }
-}"#,
- );
- }
-
- #[test]
- fn let_statement() {
- check_types(
- r#"
-#[derive(PartialEq)]
-enum Option<T> { None, Some(T) }
-
-#[derive(PartialEq)]
-struct Test { a: Option<u32>, b: u8 }
-
-fn main() {
- struct InnerStruct {}
-
- let test = 54;
- //^^^^ i32
- let test: i32 = 33;
- let mut test = 33;
- //^^^^ i32
- let _ = 22;
- let test = "test";
- //^^^^ &str
- let test = InnerStruct {};
- //^^^^ InnerStruct
-
- let test = unresolved();
-
- let test = (42, 'a');
- //^^^^ (i32, char)
- let (a, (b, (c,)) = (2, (3, (9.2,));
- //^ i32 ^ i32 ^ f64
- let &x = &92;
- //^ i32
-}"#,
- );
- }
-
- #[test]
- fn if_expr() {
- check_types(
- r#"
-//- minicore: option
-struct Test { a: Option<u32>, b: u8 }
-
-fn main() {
- let test = Some(Test { a: Some(3), b: 1 });
- //^^^^ Option<Test>
- if let None = &test {};
- if let test = &test {};
- //^^^^ &Option<Test>
- if let Some(test) = &test {};
- //^^^^ &Test
- if let Some(Test { a, b }) = &test {};
- //^ &Option<u32> ^ &u8
- if let Some(Test { a: x, b: y }) = &test {};
- //^ &Option<u32> ^ &u8
- if let Some(Test { a: Some(x), b: y }) = &test {};
- //^ &u32 ^ &u8
- if let Some(Test { a: None, b: y }) = &test {};
- //^ &u8
- if let Some(Test { b: y, .. }) = &test {};
- //^ &u8
- if test == None {}
-}"#,
- );
- }
-
- #[test]
- fn while_expr() {
- check_types(
- r#"
-//- minicore: option
-struct Test { a: Option<u32>, b: u8 }
-
-fn main() {
- let test = Some(Test { a: Some(3), b: 1 });
- //^^^^ Option<Test>
- while let Some(Test { a: Some(x), b: y }) = &test {};
- //^ &u32 ^ &u8
-}"#,
- );
- }
-
- #[test]
- fn match_arm_list() {
- check_types(
- r#"
-//- minicore: option
-struct Test { a: Option<u32>, b: u8 }
-
-fn main() {
- match Some(Test { a: Some(3), b: 1 }) {
- None => (),
- test => (),
- //^^^^ Option<Test>
- Some(Test { a: Some(x), b: y }) => (),
- //^ u32 ^ u8
- _ => {}
- }
-}"#,
- );
- }
-
- #[test]
- fn complete_for_hint() {
- check_types(
- r#"
-//- minicore: iterator
-pub struct Vec<T> {}
-
-impl<T> Vec<T> {
- pub fn new() -> Self { Vec {} }
- pub fn push(&mut self, t: T) {}
-}
-
-impl<T> IntoIterator for Vec<T> {
- type Item = T;
- type IntoIter = IntoIter<T>;
-}
-
-struct IntoIter<T> {}
-
-impl<T> Iterator for IntoIter<T> {
- type Item = T;
-}
-
-fn main() {
- let mut data = Vec::new();
- //^^^^ Vec<&str>
- data.push("foo");
- for i in data {
- //^ &str
- let z = i;
- //^ &str
- }
-}
-"#,
- );
- }
-
- #[test]
- fn multi_dyn_trait_bounds() {
- check_types(
- r#"
-pub struct Vec<T> {}
-
-impl<T> Vec<T> {
- pub fn new() -> Self { Vec {} }
-}
-
-pub struct Box<T> {}
-
-trait Display {}
-auto trait Sync {}
-
-fn main() {
- // The block expression wrapping disables the constructor hint hiding logic
- let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
- //^^ Vec<Box<&(dyn Display + Sync)>>
- let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
- //^^ Vec<Box<*const (dyn Display + Sync)>>
- let _v = { Vec::<Box<dyn Display + Sync>>::new() };
- //^^ Vec<Box<dyn Display + Sync>>
-}
-"#,
- );
- }
-
- #[test]
- fn shorten_iterator_hints() {
- check_types(
- r#"
-//- minicore: iterators
-use core::iter;
-
-struct MyIter;
-
-impl Iterator for MyIter {
- type Item = ();
- fn next(&mut self) -> Option<Self::Item> {
- None
- }
-}
-
-fn main() {
- let _x = MyIter;
- //^^ MyIter
- let _x = iter::repeat(0);
- //^^ impl Iterator<Item = i32>
- fn generic<T: Clone>(t: T) {
- let _x = iter::repeat(t);
- //^^ impl Iterator<Item = T>
- let _chained = iter::repeat(t).take(10);
- //^^^^^^^^ impl Iterator<Item = T>
- }
-}
-"#,
- );
- }
-
- #[test]
- fn skip_constructor_and_enum_type_hints() {
- check_with_config(
- InlayHintsConfig {
- type_hints: true,
- hide_named_constructor_hints: true,
- ..DISABLED_CONFIG
- },
- r#"
-//- minicore: try, option
-use core::ops::ControlFlow;
-
-mod x {
- pub mod y { pub struct Foo; }
- pub struct Foo;
- pub enum AnotherEnum {
- Variant()
- };
-}
-struct Struct;
-struct TupleStruct();
-
-impl Struct {
- fn new() -> Self {
- Struct
- }
- fn try_new() -> ControlFlow<(), Self> {
- ControlFlow::Continue(Struct)
- }
-}
-
-struct Generic<T>(T);
-impl Generic<i32> {
- fn new() -> Self {
- Generic(0)
- }
-}
-
-enum Enum {
- Variant(u32)
-}
-
-fn times2(value: i32) -> i32 {
- 2 * value
-}
-
-fn main() {
- let enumb = Enum::Variant(0);
-
- let strukt = x::Foo;
- let strukt = x::y::Foo;
- let strukt = Struct;
- let strukt = Struct::new();
-
- let tuple_struct = TupleStruct();
-
- let generic0 = Generic::new();
- // ^^^^^^^^ Generic<i32>
- let generic1 = Generic(0);
- // ^^^^^^^^ Generic<i32>
- let generic2 = Generic::<i32>::new();
- let generic3 = <Generic<i32>>::new();
- let generic4 = Generic::<i32>(0);
-
-
- let option = Some(0);
- // ^^^^^^ Option<i32>
- let func = times2;
- // ^^^^ fn times2(i32) -> i32
- let closure = |x: i32| x * 2;
- // ^^^^^^^ |i32| -> i32
-}
-
-fn fallible() -> ControlFlow<()> {
- let strukt = Struct::try_new()?;
-}
-"#,
- );
- }
-
- #[test]
- fn shows_constructor_type_hints_when_enabled() {
- check_types(
- r#"
-//- minicore: try
-use core::ops::ControlFlow;
-
-struct Struct;
-struct TupleStruct();
-
-impl Struct {
- fn new() -> Self {
- Struct
- }
- fn try_new() -> ControlFlow<(), Self> {
- ControlFlow::Continue(Struct)
- }
-}
-
-struct Generic<T>(T);
-impl Generic<i32> {
- fn new() -> Self {
- Generic(0)
- }
-}
-
-fn main() {
- let strukt = Struct::new();
- // ^^^^^^ Struct
- let tuple_struct = TupleStruct();
- // ^^^^^^^^^^^^ TupleStruct
- let generic0 = Generic::new();
- // ^^^^^^^^ Generic<i32>
- let generic1 = Generic::<i32>::new();
- // ^^^^^^^^ Generic<i32>
- let generic2 = <Generic<i32>>::new();
- // ^^^^^^^^ Generic<i32>
-}
-
-fn fallible() -> ControlFlow<()> {
- let strukt = Struct::try_new()?;
- // ^^^^^^ Struct
-}
-"#,
- );
- }
-
- #[test]
- fn closures() {
- check(
- r#"
-fn main() {
- let mut start = 0;
- //^^^^^ i32
- (0..2).for_each(|increment | { start += increment; });
- //^^^^^^^^^ i32
-
- let multiply =
- //^^^^^^^^ |i32, i32| -> i32
- | a, b| a * b
- //^ i32 ^ i32
-
- ;
-
- let _: i32 = multiply(1, 2);
- //^ a ^ b
- let multiply_ref = &multiply;
- //^^^^^^^^^^^^ &|i32, i32| -> i32
-
- let return_42 = || 42;
- //^^^^^^^^^ || -> i32
- || { 42 };
- //^^ i32
-}"#,
- );
- }
-
- #[test]
- fn return_type_hints_for_closure_without_block() {
- check_with_config(
- InlayHintsConfig {
- closure_return_type_hints: ClosureReturnTypeHints::Always,
- ..DISABLED_CONFIG
- },
- r#"
-fn main() {
- let a = || { 0 };
- //^^ i32
- let b = || 0;
- //^^ i32
-}"#,
- );
- }
-
- #[test]
- fn skip_closure_type_hints() {
- check_with_config(
- InlayHintsConfig {
- type_hints: true,
- hide_closure_initialization_hints: true,
- ..DISABLED_CONFIG
- },
- r#"
-//- minicore: fn
-fn main() {
- let multiple_2 = |x: i32| { x * 2 };
-
- let multiple_2 = |x: i32| x * 2;
- // ^^^^^^^^^^ |i32| -> i32
-
- let (not) = (|x: bool| { !x });
- // ^^^ |bool| -> bool
-
- let (is_zero, _b) = (|x: usize| { x == 0 }, false);
- // ^^^^^^^ |usize| -> bool
- // ^^ bool
-
- let plus_one = |x| { x + 1 };
- // ^ u8
- foo(plus_one);
-
- let add_mul = bar(|x: u8| { x + 1 });
- // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized
-
- let closure = if let Some(6) = add_mul(2).checked_sub(1) {
- // ^^^^^^^ fn(i32) -> i32
- |x: i32| { x * 2 }
- } else {
- |x: i32| { x * 3 }
- };
-}
-
-fn foo(f: impl FnOnce(u8) -> u8) {}
-
-fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 {
- move |x: u8| f(x) * 2
-}
-"#,
- );
- }
-
- #[test]
- fn hint_truncation() {
- check_with_config(
- InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
- r#"
-struct Smol<T>(T);
-
-struct VeryLongOuterName<T>(T);
-
-fn main() {
- let a = Smol(0u32);
- //^ Smol<u32>
- let b = VeryLongOuterName(0usize);
- //^ VeryLongOuterName<…>
- let c = Smol(Smol(0u32))
- //^ Smol<Smol<…>>
-}"#,
- );
- }
-
- // Chaining hint tests
-
- #[test]
- fn chaining_hints_ignore_comments() {
- check_expect(
- InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
- r#"
-struct A(B);
-impl A { fn into_b(self) -> B { self.0 } }
-struct B(C);
-impl B { fn into_c(self) -> C { self.0 } }
-struct C;
-
-fn main() {
- let c = A(B(C))
- .into_b() // This is a comment
- // This is another comment
- .into_c();
-}
-"#,
- expect![[r#"
- [
- InlayHint {
- range: 147..172,
- kind: ChainingHint,
- label: [
- "B",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 147..172,
- ),
- ),
- },
- InlayHint {
- range: 147..154,
- kind: ChainingHint,
- label: [
- "A",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 147..154,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn chaining_hints_without_newlines() {
- check_chains(
- r#"
-struct A(B);
-impl A { fn into_b(self) -> B { self.0 } }
-struct B(C);
-impl B { fn into_c(self) -> C { self.0 } }
-struct C;
-
-fn main() {
- let c = A(B(C)).into_b().into_c();
-}"#,
- );
- }
-
- #[test]
- fn struct_access_chaining_hints() {
- check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
- r#"
-struct A { pub b: B }
-struct B { pub c: C }
-struct C(pub bool);
-struct D;
-
-impl D {
- fn foo(&self) -> i32 { 42 }
-}
-
-fn main() {
- let x = A { b: B { c: C(true) } }
- .b
- .c
- .0;
- let x = D
- .foo();
-}"#,
- expect![[r#"
- [
- InlayHint {
- range: 143..190,
- kind: ChainingHint,
- label: [
- "C",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 143..190,
- ),
- ),
- },
- InlayHint {
- range: 143..179,
- kind: ChainingHint,
- label: [
- "B",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 143..179,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn generic_chaining_hints() {
- check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
- r#"
-struct A<T>(T);
-struct B<T>(T);
-struct C<T>(T);
-struct X<T,R>(T, R);
-
-impl<T> A<T> {
- fn new(t: T) -> Self { A(t) }
- fn into_b(self) -> B<T> { B(self.0) }
-}
-impl<T> B<T> {
- fn into_c(self) -> C<T> { C(self.0) }
-}
-fn main() {
- let c = A::new(X(42, true))
- .into_b()
- .into_c();
-}
-"#,
- expect![[r#"
- [
- InlayHint {
- range: 246..283,
- kind: ChainingHint,
- label: [
- "B<X<i32, bool>>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 246..283,
- ),
- ),
- },
- InlayHint {
- range: 246..265,
- kind: ChainingHint,
- label: [
- "A<X<i32, bool>>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 246..265,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn shorten_iterator_chaining_hints() {
- check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
- r#"
-//- minicore: iterators
-use core::iter;
-
-struct MyIter;
-
-impl Iterator for MyIter {
- type Item = ();
- fn next(&mut self) -> Option<Self::Item> {
- None
- }
-}
-
-fn main() {
- let _x = MyIter.by_ref()
- .take(5)
- .by_ref()
- .take(5)
- .by_ref();
-}
-"#,
- expect![[r#"
- [
- InlayHint {
- range: 174..241,
- kind: ChainingHint,
- label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 174..241,
- ),
- ),
- },
- InlayHint {
- range: 174..224,
- kind: ChainingHint,
- label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 174..224,
- ),
- ),
- },
- InlayHint {
- range: 174..206,
- kind: ChainingHint,
- label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 174..206,
- ),
- ),
- },
- InlayHint {
- range: 174..189,
- kind: ChainingHint,
- label: [
- "&mut MyIter",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 174..189,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn hints_in_attr_call() {
- check_expect(
- TEST_CONFIG,
- r#"
-//- proc_macros: identity, input_replace
-struct Struct;
-impl Struct {
- fn chain(self) -> Self {
- self
- }
-}
-#[proc_macros::identity]
-fn main() {
- let strukt = Struct;
- strukt
- .chain()
- .chain()
- .chain();
- Struct::chain(strukt);
-}
-"#,
- expect![[r#"
- [
- InlayHint {
- range: 124..130,
- kind: TypeHint,
- label: [
- "Struct",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 124..130,
- ),
- ),
- },
- InlayHint {
- range: 145..185,
- kind: ChainingHint,
- label: [
- "Struct",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 145..185,
- ),
- ),
- },
- InlayHint {
- range: 145..168,
- kind: ChainingHint,
- label: [
- "Struct",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 145..168,
- ),
- ),
- },
- InlayHint {
- range: 222..228,
- kind: ParameterHint,
- label: [
- "self",
- ],
- tooltip: Some(
- HoverOffset(
- FileId(
- 0,
- ),
- 42,
- ),
- ),
- },
- ]
- "#]],
- );
- }
-
- #[test]
- fn hints_lifetimes() {
- check(
- r#"
-fn empty() {}
-
-fn no_gpl(a: &()) {}
- //^^^^^^<'0>
- // ^'0
-fn empty_gpl<>(a: &()) {}
- // ^'0 ^'0
-fn partial<'b>(a: &(), b: &'b ()) {}
-// ^'0, $ ^'0
-fn partial<'a>(a: &'a (), b: &()) {}
-// ^'0, $ ^'0
-
-fn single_ret(a: &()) -> &() {}
-// ^^^^^^^^^^<'0>
- // ^'0 ^'0
-fn full_mul(a: &(), b: &()) {}
-// ^^^^^^^^<'0, '1>
- // ^'0 ^'1
-
-fn foo<'c>(a: &'c ()) -> &() {}
- // ^'c
-
-fn nested_in(a: & &X< &()>) {}
-// ^^^^^^^^^<'0, '1, '2>
- //^'0 ^'1 ^'2
-fn nested_out(a: &()) -> & &X< &()>{}
-// ^^^^^^^^^^<'0>
- //^'0 ^'0 ^'0 ^'0
-
-impl () {
- fn foo(&self) {}
- // ^^^<'0>
- // ^'0
- fn foo(&self) -> &() {}
- // ^^^<'0>
- // ^'0 ^'0
- fn foo(&self, a: &()) -> &() {}
- // ^^^<'0, '1>
- // ^'0 ^'1 ^'0
-}
-"#,
- );
- }
-
- #[test]
- fn hints_lifetimes_named() {
- check_with_config(
- InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
- r#"
-fn nested_in<'named>(named: & &X< &()>) {}
-// ^'named1, 'named2, 'named3, $
- //^'named1 ^'named2 ^'named3
-"#,
- );
- }
-
- #[test]
- fn hints_lifetimes_trivial_skip() {
- check_with_config(
- InlayHintsConfig {
- lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
- ..TEST_CONFIG
- },
- r#"
-fn no_gpl(a: &()) {}
-fn empty_gpl<>(a: &()) {}
-fn partial<'b>(a: &(), b: &'b ()) {}
-fn partial<'a>(a: &'a (), b: &()) {}
-
-fn single_ret(a: &()) -> &() {}
-// ^^^^^^^^^^<'0>
- // ^'0 ^'0
-fn full_mul(a: &(), b: &()) {}
-
-fn foo<'c>(a: &'c ()) -> &() {}
- // ^'c
-
-fn nested_in(a: & &X< &()>) {}
-fn nested_out(a: &()) -> & &X< &()>{}
-// ^^^^^^^^^^<'0>
- //^'0 ^'0 ^'0 ^'0
-
-impl () {
- fn foo(&self) {}
- fn foo(&self) -> &() {}
- // ^^^<'0>
- // ^'0 ^'0
- fn foo(&self, a: &()) -> &() {}
- // ^^^<'0, '1>
- // ^'0 ^'1 ^'0
-}
-"#,
- );
- }
-
- #[test]
- fn hints_lifetimes_static() {
- check_with_config(
- InlayHintsConfig {
- lifetime_elision_hints: LifetimeElisionHints::Always,
- ..TEST_CONFIG
- },
- r#"
-trait Trait {}
-static S: &str = "";
-// ^'static
-const C: &str = "";
-// ^'static
-const C: &dyn Trait = panic!();
-// ^'static
-
-impl () {
- const C: &str = "";
- const C: &dyn Trait = panic!();
-}
-"#,
- );
- }
-
- #[test]
- fn hints_binding_modes() {
- check_with_config(
- InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
- r#"
-fn __(
- (x,): (u32,),
- (x,): &(u32,),
- //^^^^&
- //^ ref
- (x,): &mut (u32,)
- //^^^^&mut
- //^ ref mut
-) {
- let (x,) = (0,);
- let (x,) = &(0,);
- //^^^^ &
- //^ ref
- let (x,) = &mut (0,);
- //^^^^ &mut
- //^ ref mut
- let &mut (x,) = &mut (0,);
- let (ref mut x,) = &mut (0,);
- //^^^^^^^^^^^^ &mut
- let &mut (ref mut x,) = &mut (0,);
- let (mut x,) = &mut (0,);
- //^^^^^^^^ &mut
- match (0,) {
- (x,) => ()
- }
- match &(0,) {
- (x,) => ()
- //^^^^ &
- //^ ref
- }
- match &mut (0,) {
- (x,) => ()
- //^^^^ &mut
- //^ ref mut
- }
-}"#,
- );
- }
-
- #[test]
- fn hints_closing_brace() {
- check_with_config(
- InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
- r#"
-fn a() {}
-
-fn f() {
-} // no hint unless `}` is the last token on the line
-
-fn g() {
- }
-//^ fn g
-
-fn h<T>(with: T, arguments: u8, ...) {
- }
-//^ fn h
-
-trait Tr {
- fn f();
- fn g() {
- }
- //^ fn g
- }
-//^ trait Tr
-impl Tr for () {
- }
-//^ impl Tr for ()
-impl dyn Tr {
- }
-//^ impl dyn Tr
-
-static S0: () = 0;
-static S1: () = {};
-static S2: () = {
- };
-//^ static S2
-const _: () = {
- };
-//^ const _
-
-mod m {
- }
-//^ mod m
-
-m! {}
-m!();
-m!(
- );
-//^ m!
-
-m! {
- }
-//^ m!
-
-fn f() {
- let v = vec![
- ];
- }
-//^ fn f
-"#,
- );
- }
-
- #[test]
- fn adjustment_hints() {
- check_with_config(
- InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
- r#"
-//- minicore: coerce_unsized
-fn main() {
- let _: u32 = loop {};
- //^^^^^^^<never-to-any>
- let _: &u32 = &mut 0;
- //^^^^^^&
- //^^^^^^*
- let _: &mut u32 = &mut 0;
- //^^^^^^&mut $
- //^^^^^^*
- let _: *const u32 = &mut 0;
- //^^^^^^&raw const $
- //^^^^^^*
- let _: *mut u32 = &mut 0;
- //^^^^^^&raw mut $
- //^^^^^^*
- let _: fn() = main;
- //^^^^<fn-item-to-fn-pointer>
- let _: unsafe fn() = main;
- //^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
- //^^^^<fn-item-to-fn-pointer>
- let _: unsafe fn() = main as fn();
- //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
- let _: fn() = || {};
- //^^^^^<closure-to-fn-pointer>
- let _: unsafe fn() = || {};
- //^^^^^<closure-to-unsafe-fn-pointer>
- let _: *const u32 = &mut 0u32 as *mut u32;
- //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
- let _: &mut [_] = &mut [0; 0];
- //^^^^^^^^^^^<unsize>
- //^^^^^^^^^^^&mut $
- //^^^^^^^^^^^*
-
- Struct.consume();
- Struct.by_ref();
- //^^^^^^(
- //^^^^^^&
- //^^^^^^)
- Struct.by_ref_mut();
- //^^^^^^(
- //^^^^^^&mut $
- //^^^^^^)
-
- (&Struct).consume();
- //^^^^^^^*
- (&Struct).by_ref();
-
- (&mut Struct).consume();
- //^^^^^^^^^^^*
- (&mut Struct).by_ref();
- //^^^^^^^^^^^&
- //^^^^^^^^^^^*
- (&mut Struct).by_ref_mut();
-}
-
-#[derive(Copy, Clone)]
-struct Struct;
-impl Struct {
- fn consume(self) {}
- fn by_ref(&self) {}
- fn by_ref_mut(&mut self) {}
-}
-"#,
- )
- }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
new file mode 100644
index 000000000..bdd7c05e0
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
@@ -0,0 +1,630 @@
+//! Implementation of "adjustment" inlay hints:
+//! ```no_run
+//! let _: u32 = /* <never-to-any> */ loop {};
+//! let _: &u32 = /* &* */ &mut 0;
+//! ```
+use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics};
+use ide_db::RootDatabase;
+
+use syntax::{
+ ast::{self, make, AstNode},
+ ted,
+};
+
+use crate::{AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintsConfig, InlayKind};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if config.adjustment_hints_hide_outside_unsafe && !sema.is_inside_unsafe(expr) {
+ return None;
+ }
+
+ if config.adjustment_hints == AdjustmentHints::Never {
+ return None;
+ }
+
+ // These inherit from the inner expression which would result in duplicate hints
+ if let ast::Expr::ParenExpr(_)
+ | ast::Expr::IfExpr(_)
+ | ast::Expr::BlockExpr(_)
+ | ast::Expr::MatchExpr(_) = expr
+ {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+ let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
+
+ let (postfix, needs_outer_parens, needs_inner_parens) =
+ mode_and_needs_parens_for_adjustment_hints(expr, config.adjustment_hints_mode);
+
+ if needs_outer_parens {
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::OpeningParenthesis,
+ label: "(".into(),
+ tooltip: None,
+ });
+ }
+
+ if postfix && needs_inner_parens {
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::OpeningParenthesis,
+ label: "(".into(),
+ tooltip: None,
+ });
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ClosingParenthesis,
+ label: ")".into(),
+ tooltip: None,
+ });
+ }
+
+ let (mut tmp0, mut tmp1);
+ let iter: &mut dyn Iterator<Item = _> = if postfix {
+ tmp0 = adjustments.into_iter();
+ &mut tmp0
+ } else {
+ tmp1 = adjustments.into_iter().rev();
+ &mut tmp1
+ };
+
+ for adjustment in iter {
+ if adjustment.source == adjustment.target {
+ continue;
+ }
+
+ // FIXME: Add some nicer tooltips to each of these
+ let text = match adjustment.kind {
+ Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
+ "<never-to-any>"
+ }
+ Adjust::Deref(None) => "*",
+ Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => "*",
+ Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => "*",
+ Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => "&",
+ Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => "&mut ",
+ Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => "&raw const ",
+ Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => "&raw mut ",
+ // some of these could be represented via `as` casts, but that's not too nice and
+ // handling everything as a prefix expr makes the `(` and `)` insertion easier
+ Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
+ match cast {
+ PointerCast::ReifyFnPointer => "<fn-item-to-fn-pointer>",
+ PointerCast::UnsafeFnPointer => "<safe-fn-pointer-to-unsafe-fn-pointer>",
+ PointerCast::ClosureFnPointer(Safety::Unsafe) => {
+ "<closure-to-unsafe-fn-pointer>"
+ }
+ PointerCast::ClosureFnPointer(Safety::Safe) => "<closure-to-fn-pointer>",
+ PointerCast::MutToConstPointer => "<mut-ptr-to-const-ptr>",
+ PointerCast::ArrayToPointer => "<array-ptr-to-element-ptr>",
+ PointerCast::Unsize => "<unsize>",
+ }
+ }
+ _ => continue,
+ };
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: if postfix {
+ InlayKind::AdjustmentHintPostfix
+ } else {
+ InlayKind::AdjustmentHint
+ },
+ label: if postfix { format!(".{}", text.trim_end()).into() } else { text.into() },
+ tooltip: None,
+ });
+ }
+ if !postfix && needs_inner_parens {
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::OpeningParenthesis,
+ label: "(".into(),
+ tooltip: None,
+ });
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ClosingParenthesis,
+ label: ")".into(),
+ tooltip: None,
+ });
+ }
+ if needs_outer_parens {
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ClosingParenthesis,
+ label: ")".into(),
+ tooltip: None,
+ });
+ }
+ Some(())
+}
+
+/// Returns whatever the hint should be postfix and if we need to add paretheses on the inside and/or outside of `expr`,
+/// if we are going to add (`postfix`) adjustments hints to it.
+fn mode_and_needs_parens_for_adjustment_hints(
+ expr: &ast::Expr,
+ mode: AdjustmentHintsMode,
+) -> (bool, bool, bool) {
+ use {std::cmp::Ordering::*, AdjustmentHintsMode::*};
+
+ match mode {
+ Prefix | Postfix => {
+ let postfix = matches!(mode, Postfix);
+ let (inside, outside) = needs_parens_for_adjustment_hints(expr, postfix);
+ (postfix, inside, outside)
+ }
+ PreferPrefix | PreferPostfix => {
+ let prefer_postfix = matches!(mode, PreferPostfix);
+
+ let (pre_inside, pre_outside) = needs_parens_for_adjustment_hints(expr, false);
+ let prefix = (false, pre_inside, pre_outside);
+ let pre_count = pre_inside as u8 + pre_outside as u8;
+
+ let (post_inside, post_outside) = needs_parens_for_adjustment_hints(expr, true);
+ let postfix = (true, post_inside, post_outside);
+ let post_count = post_inside as u8 + post_outside as u8;
+
+ match pre_count.cmp(&post_count) {
+ Less => prefix,
+ Greater => postfix,
+ Equal if prefer_postfix => postfix,
+ Equal => prefix,
+ }
+ }
+ }
+}
+
+/// Returns whatever we need to add paretheses on the inside and/or outside of `expr`,
+/// if we are going to add (`postfix`) adjustments hints to it.
+fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
+ // This is a very miserable pile of hacks...
+ //
+ // `Expr::needs_parens_in` requires that the expression is the child of the other expression,
+ // that is supposed to be its parent.
+ //
+ // But we want to check what would happen if we add `*`/`.*` to the inner expression.
+ // To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
+ // to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
+ // where "expr" is the `expr` parameter, `*expr` is the editted `expr`,
+ // and "parent" is the parent of the original expression...
+ //
+ // For this we utilize mutable mutable trees, which is a HACK, but it works.
+ //
+ // FIXME: comeup with a better API for `needs_parens_in`, so that we don't have to do *this*
+
+ // Make `&expr`/`expr?`
+ let dummy_expr = {
+ // `make::*` function go through a string, so they parse wrongly.
+ // for example `` make::expr_try(`|| a`) `` would result in a
+ // `|| (a?)` and not `(|| a)?`.
+ //
+ // Thus we need dummy parens to preserve the relationship we want.
+ // The parens are then simply ignored by the following code.
+ let dummy_paren = make::expr_paren(expr.clone());
+ if postfix {
+ make::expr_try(dummy_paren)
+ } else {
+ make::expr_ref(dummy_paren, false)
+ }
+ };
+
+ // Do the dark mutable tree magic.
+ // This essentially makes `dummy_expr` and `expr` switch places (families),
+ // so that `expr`'s parent is not `dummy_expr`'s parent.
+ let dummy_expr = dummy_expr.clone_for_update();
+ let expr = expr.clone_for_update();
+ ted::replace(expr.syntax(), dummy_expr.syntax());
+
+ let parent = dummy_expr.syntax().parent();
+ let expr = if postfix {
+ let ast::Expr::TryExpr(e) = &dummy_expr else { unreachable!() };
+ let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+
+ e.expr().unwrap()
+ } else {
+ let ast::Expr::RefExpr(e) = &dummy_expr else { unreachable!() };
+ let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
+
+ e.expr().unwrap()
+ };
+
+ // At this point
+ // - `parent` is the parrent of the original expression
+ // - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
+ // - `expr` is the clone of the original expression (with `dummy_expr` as the parent)
+
+ let needs_outer_parens = parent.map_or(false, |p| dummy_expr.needs_parens_in(p));
+ let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
+
+ (needs_outer_parens, needs_inner_parens)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+ AdjustmentHints, AdjustmentHintsMode, InlayHintsConfig,
+ };
+
+ #[test]
+ fn adjustment_hints() {
+ check_with_config(
+ InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
+ r#"
+//- minicore: coerce_unsized, fn
+fn main() {
+ let _: u32 = loop {};
+ //^^^^^^^<never-to-any>
+ let _: &u32 = &mut 0;
+ //^^^^^^&
+ //^^^^^^*
+ let _: &mut u32 = &mut 0;
+ //^^^^^^&mut $
+ //^^^^^^*
+ let _: *const u32 = &mut 0;
+ //^^^^^^&raw const $
+ //^^^^^^*
+ let _: *mut u32 = &mut 0;
+ //^^^^^^&raw mut $
+ //^^^^^^*
+ let _: fn() = main;
+ //^^^^<fn-item-to-fn-pointer>
+ let _: unsafe fn() = main;
+ //^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
+ //^^^^<fn-item-to-fn-pointer>
+ let _: unsafe fn() = main as fn();
+ //^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
+ //^^^^^^^^^^^^(
+ //^^^^^^^^^^^^)
+ let _: fn() = || {};
+ //^^^^^<closure-to-fn-pointer>
+ let _: unsafe fn() = || {};
+ //^^^^^<closure-to-unsafe-fn-pointer>
+ let _: *const u32 = &mut 0u32 as *mut u32;
+ //^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
+ //^^^^^^^^^^^^^^^^^^^^^(
+ //^^^^^^^^^^^^^^^^^^^^^)
+ let _: &mut [_] = &mut [0; 0];
+ //^^^^^^^^^^^<unsize>
+ //^^^^^^^^^^^&mut $
+ //^^^^^^^^^^^*
+
+ Struct.consume();
+ Struct.by_ref();
+ //^^^^^^(
+ //^^^^^^&
+ //^^^^^^)
+ Struct.by_ref_mut();
+ //^^^^^^(
+ //^^^^^^&mut $
+ //^^^^^^)
+
+ (&Struct).consume();
+ //^^^^^^^*
+ (&Struct).by_ref();
+
+ (&mut Struct).consume();
+ //^^^^^^^^^^^*
+ (&mut Struct).by_ref();
+ //^^^^^^^^^^^&
+ //^^^^^^^^^^^*
+ (&mut Struct).by_ref_mut();
+
+ // Check that block-like expressions don't duplicate hints
+ let _: &mut [u32] = (&mut []);
+ //^^^^^^^<unsize>
+ //^^^^^^^&mut $
+ //^^^^^^^*
+ let _: &mut [u32] = { &mut [] };
+ //^^^^^^^<unsize>
+ //^^^^^^^&mut $
+ //^^^^^^^*
+ let _: &mut [u32] = unsafe { &mut [] };
+ //^^^^^^^<unsize>
+ //^^^^^^^&mut $
+ //^^^^^^^*
+ let _: &mut [u32] = if true {
+ &mut []
+ //^^^^^^^<unsize>
+ //^^^^^^^&mut $
+ //^^^^^^^*
+ } else {
+ loop {}
+ //^^^^^^^<never-to-any>
+ };
+ let _: &mut [u32] = match () { () => &mut [] }
+ //^^^^^^^<unsize>
+ //^^^^^^^&mut $
+ //^^^^^^^*
+
+ let _: &mut dyn Fn() = &mut || ();
+ //^^^^^^^^^^<unsize>
+ //^^^^^^^^^^&mut $
+ //^^^^^^^^^^*
+}
+
+#[derive(Copy, Clone)]
+struct Struct;
+impl Struct {
+ fn consume(self) {}
+ fn by_ref(&self) {}
+ fn by_ref_mut(&mut self) {}
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn adjustment_hints_postfix() {
+ check_with_config(
+ InlayHintsConfig {
+ adjustment_hints: AdjustmentHints::Always,
+ adjustment_hints_mode: AdjustmentHintsMode::Postfix,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: coerce_unsized, fn
+fn main() {
+
+ Struct.consume();
+ Struct.by_ref();
+ //^^^^^^.&
+ Struct.by_ref_mut();
+ //^^^^^^.&mut
+
+ (&Struct).consume();
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ (&Struct).by_ref();
+
+ (&mut Struct).consume();
+ //^^^^^^^^^^^(
+ //^^^^^^^^^^^)
+ //^^^^^^^^^^^.*
+ (&mut Struct).by_ref();
+ //^^^^^^^^^^^(
+ //^^^^^^^^^^^)
+ //^^^^^^^^^^^.*
+ //^^^^^^^^^^^.&
+ (&mut Struct).by_ref_mut();
+
+ // Check that block-like expressions don't duplicate hints
+ let _: &mut [u32] = (&mut []);
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ //^^^^^^^.&mut
+ //^^^^^^^.<unsize>
+ let _: &mut [u32] = { &mut [] };
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ //^^^^^^^.&mut
+ //^^^^^^^.<unsize>
+ let _: &mut [u32] = unsafe { &mut [] };
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ //^^^^^^^.&mut
+ //^^^^^^^.<unsize>
+ let _: &mut [u32] = if true {
+ &mut []
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ //^^^^^^^.&mut
+ //^^^^^^^.<unsize>
+ } else {
+ loop {}
+ //^^^^^^^.<never-to-any>
+ };
+ let _: &mut [u32] = match () { () => &mut [] }
+ //^^^^^^^(
+ //^^^^^^^)
+ //^^^^^^^.*
+ //^^^^^^^.&mut
+ //^^^^^^^.<unsize>
+
+ let _: &mut dyn Fn() = &mut || ();
+ //^^^^^^^^^^(
+ //^^^^^^^^^^)
+ //^^^^^^^^^^.*
+ //^^^^^^^^^^.&mut
+ //^^^^^^^^^^.<unsize>
+}
+
+#[derive(Copy, Clone)]
+struct Struct;
+impl Struct {
+ fn consume(self) {}
+ fn by_ref(&self) {}
+ fn by_ref_mut(&mut self) {}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn adjustment_hints_prefer_prefix() {
+ check_with_config(
+ InlayHintsConfig {
+ adjustment_hints: AdjustmentHints::Always,
+ adjustment_hints_mode: AdjustmentHintsMode::PreferPrefix,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn main() {
+ let _: u32 = loop {};
+ //^^^^^^^<never-to-any>
+
+ Struct.by_ref();
+ //^^^^^^.&
+
+ let (): () = return ();
+ //^^^^^^^^^<never-to-any>
+
+ struct Struct;
+ impl Struct { fn by_ref(&self) {} }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn adjustment_hints_prefer_postfix() {
+ check_with_config(
+ InlayHintsConfig {
+ adjustment_hints: AdjustmentHints::Always,
+ adjustment_hints_mode: AdjustmentHintsMode::PreferPostfix,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn main() {
+ let _: u32 = loop {};
+ //^^^^^^^.<never-to-any>
+
+ Struct.by_ref();
+ //^^^^^^.&
+
+ let (): () = return ();
+ //^^^^^^^^^<never-to-any>
+
+ struct Struct;
+ impl Struct { fn by_ref(&self) {} }
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn never_to_never_is_never_shown() {
+ check_with_config(
+ InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
+ r#"
+fn never() -> ! {
+ return loop {};
+}
+
+fn or_else() {
+ let () = () else { return };
+}
+ "#,
+ )
+ }
+
+ #[test]
+ fn adjustment_hints_unsafe_only() {
+ check_with_config(
+ InlayHintsConfig {
+ adjustment_hints: AdjustmentHints::Always,
+ adjustment_hints_hide_outside_unsafe: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+unsafe fn enabled() {
+ f(&&());
+ //^^^^&
+ //^^^^*
+ //^^^^*
+}
+
+fn disabled() {
+ f(&&());
+}
+
+fn mixed() {
+ f(&&());
+
+ unsafe {
+ f(&&());
+ //^^^^&
+ //^^^^*
+ //^^^^*
+ }
+}
+
+const _: () = {
+ f(&&());
+
+ unsafe {
+ f(&&());
+ //^^^^&
+ //^^^^*
+ //^^^^*
+ }
+};
+
+static STATIC: () = {
+ f(&&());
+
+ unsafe {
+ f(&&());
+ //^^^^&
+ //^^^^*
+ //^^^^*
+ }
+};
+
+enum E {
+ Disable = { f(&&()); 0 },
+ Enable = unsafe { f(&&()); 1 },
+ //^^^^&
+ //^^^^*
+ //^^^^*
+}
+
+const fn f(_: &()) {}
+ "#,
+ )
+ }
+
+ #[test]
+ fn adjustment_hints_unsafe_only_with_item() {
+ check_with_config(
+ InlayHintsConfig {
+ adjustment_hints: AdjustmentHints::Always,
+ adjustment_hints_hide_outside_unsafe: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn a() {
+ struct Struct;
+ impl Struct {
+ fn by_ref(&self) {}
+ }
+
+ _ = Struct.by_ref();
+
+ _ = unsafe { Struct.by_ref() };
+ //^^^^^^(
+ //^^^^^^&
+ //^^^^^^)
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn bug() {
+ check_with_config(
+ InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
+ r#"
+fn main() {
+ // These should be identical, but they are not...
+
+ let () = return;
+ let (): () = return;
+ //^^^^^^<never-to-any>
+}
+ "#,
+ )
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
new file mode 100644
index 000000000..adec19c76
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs
@@ -0,0 +1,978 @@
+//! Implementation of "type" inlay hints:
+//! ```no_run
+//! fn f(a: i32, b: i32) -> i32 { a + b }
+//! let _x /* i32 */= f(4, 4);
+//! ```
+use hir::{Semantics, TypeInfo};
+use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
+
+use itertools::Itertools;
+use syntax::{
+ ast::{self, AstNode, HasName},
+ match_ast,
+};
+
+use crate::{
+ inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
+};
+
+use super::label_of_ty;
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ pat: &ast::IdentPat,
+) -> Option<()> {
+ if !config.type_hints {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(pat.clone()).pop();
+ let desc_pat = descended.as_ref().unwrap_or(pat);
+ let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
+
+ if should_not_display_type_hint(sema, config, pat, &ty) {
+ return None;
+ }
+
+ let label = label_of_ty(famous_defs, config, ty)?;
+
+ if config.hide_named_constructor_hints
+ && is_named_constructor(sema, pat, &label.to_string()).is_some()
+ {
+ return None;
+ }
+
+ acc.push(InlayHint {
+ range: match pat.name() {
+ Some(name) => name.syntax().text_range(),
+ None => pat.syntax().text_range(),
+ },
+ kind: InlayKind::TypeHint,
+ label,
+ tooltip: pat
+ .name()
+ .map(|it| it.syntax().text_range())
+ .map(|it| InlayTooltip::HoverRanged(file_id, it)),
+ });
+
+ Some(())
+}
+
+fn should_not_display_type_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ bind_pat: &ast::IdentPat,
+ pat_ty: &hir::Type,
+) -> bool {
+ let db = sema.db;
+
+ if pat_ty.is_unknown() {
+ return true;
+ }
+
+ if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
+ if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
+ return true;
+ }
+ }
+
+ if config.hide_closure_initialization_hints {
+ if let Some(parent) = bind_pat.syntax().parent() {
+ if let Some(it) = ast::LetStmt::cast(parent) {
+ if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
+ if closure_has_block_body(&closure) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ for node in bind_pat.syntax().ancestors() {
+ match_ast! {
+ match node {
+ ast::LetStmt(it) => return it.ty().is_some(),
+ // FIXME: We might wanna show type hints in parameters for non-top level patterns as well
+ ast::Param(it) => return it.ty().is_some(),
+ ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
+ ast::IfExpr(_) => return false,
+ ast::WhileExpr(_) => return false,
+ ast::ForExpr(it) => {
+ // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
+ // Type of expr should be iterable.
+ return it.in_token().is_none() ||
+ it.iterable()
+ .and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
+ .map(TypeInfo::original)
+ .map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
+ },
+ _ => (),
+ }
+ }
+ }
+ false
+}
+
+fn is_named_constructor(
+ sema: &Semantics<'_, RootDatabase>,
+ pat: &ast::IdentPat,
+ ty_name: &str,
+) -> Option<()> {
+ let let_node = pat.syntax().parent()?;
+ let expr = match_ast! {
+ match let_node {
+ ast::LetStmt(it) => it.initializer(),
+ ast::LetExpr(it) => it.expr(),
+ _ => None,
+ }
+ }?;
+
+ let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
+ // unwrap postfix expressions
+ let expr = match expr {
+ ast::Expr::TryExpr(it) => it.expr(),
+ ast::Expr::AwaitExpr(it) => it.expr(),
+ expr => Some(expr),
+ }?;
+ let expr = match expr {
+ ast::Expr::CallExpr(call) => match call.expr()? {
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ },
+ ast::Expr::PathExpr(path) => path,
+ _ => return None,
+ };
+ let path = expr.path()?;
+
+ let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
+ let callable_kind = callable.map(|it| it.kind());
+ let qual_seg = match callable_kind {
+ Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
+ path.qualifier()?.segment()
+ }
+ _ => path.segment(),
+ }?;
+
+ let ctor_name = match qual_seg.kind()? {
+ ast::PathSegmentKind::Name(name_ref) => {
+ match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
+ Some(generics) => format!("{name_ref}<{}>", generics.format(", ")),
+ None => name_ref.to_string(),
+ }
+ }
+ ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
+ _ => return None,
+ };
+ (ctor_name == ty_name).then_some(())
+}
+
+fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
+ if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
+ let pat_text = bind_pat.to_string();
+ enum_data
+ .variants(db)
+ .into_iter()
+ .map(|variant| variant.name(db).to_smol_str())
+ .any(|enum_name| enum_name == pat_text)
+ } else {
+ false
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ // This module also contains tests for super::closure_ret
+
+ use expect_test::expect;
+ use syntax::{TextRange, TextSize};
+ use test_utils::extract_annotations;
+
+ use crate::{fixture, inlay_hints::InlayHintsConfig};
+
+ use crate::inlay_hints::tests::{
+ check, check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS,
+ TEST_CONFIG,
+ };
+ use crate::ClosureReturnTypeHints;
+
+ #[track_caller]
+ fn check_types(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[test]
+ fn type_hints_only() {
+ check_types(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(4, 4);
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn type_hints_bindings_after_at() {
+ check_types(
+ r#"
+//- minicore: option
+fn main() {
+ let ref foo @ bar @ ref mut baz = 0;
+ //^^^ &i32
+ //^^^ i32
+ //^^^ &mut i32
+ let [x @ ..] = [0];
+ //^ [i32; 1]
+ if let x @ Some(_) = Some(0) {}
+ //^ Option<i32>
+ let foo @ (bar, baz) = (3, 3);
+ //^^^ (i32, i32)
+ //^^^ i32
+ //^^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn default_generic_types_should_not_be_displayed() {
+ check(
+ r#"
+struct Test<K, T = u8> { k: K, t: T }
+
+fn main() {
+ let zz = Test { t: 23u8, k: 33 };
+ //^^ Test<i32>
+ let zz_ref = &zz;
+ //^^^^^^ &Test<i32>
+ let test = || zz;
+ //^^^^ || -> Test<i32>
+}"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterators_in_associated_params() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+pub struct SomeIter<T> {}
+
+impl<T> SomeIter<T> {
+ pub fn new() -> Self { SomeIter {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> Iterator for SomeIter<T> {
+ type Item = T;
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let mut some_iter = SomeIter::new();
+ //^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
+ some_iter.push(iter::repeat(2).take(2));
+ let iter_of_iters = some_iter.take(2);
+ //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn iterator_hint_regression_issue_12674() {
+ // Ensure we don't crash while solving the projection type of iterators.
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ r#"
+//- minicore: iterators
+struct S<T>(T);
+impl<T> S<T> {
+ fn iter(&self) -> Iter<'_, T> { loop {} }
+}
+struct Iter<'a, T: 'a>(&'a T);
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = &'a T;
+ fn next(&mut self) -> Option<Self::Item> { loop {} }
+}
+struct Container<'a> {
+ elements: S<&'a str>,
+}
+struct SliceIter<'a, T>(&'a T);
+impl<'a, T> Iterator for SliceIter<'a, T> {
+ type Item = &'a T;
+ fn next(&mut self) -> Option<Self::Item> { loop {} }
+}
+
+fn main(a: SliceIter<'_, Container>) {
+ a
+ .filter_map(|c| Some(c.elements.iter().filter_map(|v| Some(v))))
+ .map(|e| e);
+}
+ "#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 484..554,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = impl Iterator<Item = &&str>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 484..554,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 484..485,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "SliceIter",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 289..298,
+ },
+ ),
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Container",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 238..247,
+ },
+ ),
+ },
+ ">",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 484..485,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn infer_call_method_return_associated_types_with_generic() {
+ check_types(
+ r#"
+ pub trait Default {
+ fn default() -> Self;
+ }
+ pub trait Foo {
+ type Bar: Default;
+ }
+
+ pub fn quux<T: Foo>() -> T::Bar {
+ let y = Default::default();
+ //^ <T as Foo>::Bar
+
+ y
+ }
+ "#,
+ );
+ }
+
+ #[test]
+ fn fn_hints() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+fn foo() -> impl Fn() { loop {} }
+fn foo1() -> impl Fn(f64) { loop {} }
+fn foo2() -> impl Fn(f64, f64) { loop {} }
+fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+fn main() {
+ let foo = foo();
+ // ^^^ impl Fn()
+ let foo = foo1();
+ // ^^^ impl Fn(f64)
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ // ^^^ &dyn Fn(f64, f64) -> u32
+ let foo = foo5();
+ // ^^^ &dyn Fn(&dyn Fn(f64, f64) -> u32, f64) -> u32
+ let foo = foo6();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo7();
+ // ^^^ *const impl Fn(f64, f64) -> u32
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn check_hint_range_limit() {
+ let fixture = r#"
+ //- minicore: fn, sized
+ fn foo() -> impl Fn() { loop {} }
+ fn foo1() -> impl Fn(f64) { loop {} }
+ fn foo2() -> impl Fn(f64, f64) { loop {} }
+ fn foo3() -> impl Fn(f64, f64) -> u32 { loop {} }
+ fn foo4() -> &'static dyn Fn(f64, f64) -> u32 { loop {} }
+ fn foo5() -> &'static dyn Fn(&'static dyn Fn(f64, f64) -> u32, f64) -> u32 { loop {} }
+ fn foo6() -> impl Fn(f64, f64) -> u32 + Sized { loop {} }
+ fn foo7() -> *const (impl Fn(f64, f64) -> u32 + Sized) { loop {} }
+
+ fn main() {
+ let foo = foo();
+ let foo = foo1();
+ let foo = foo2();
+ // ^^^ impl Fn(f64, f64)
+ let foo = foo3();
+ // ^^^ impl Fn(f64, f64) -> u32
+ let foo = foo4();
+ let foo = foo5();
+ let foo = foo6();
+ let foo = foo7();
+ }
+ "#;
+ let (analysis, file_id) = fixture::file(fixture);
+ let expected = extract_annotations(&analysis.file_text(file_id).unwrap());
+ let inlay_hints = analysis
+ .inlay_hints(
+ &InlayHintsConfig { type_hints: true, ..DISABLED_CONFIG },
+ file_id,
+ Some(TextRange::new(TextSize::from(500), TextSize::from(600))),
+ )
+ .unwrap();
+ let actual =
+ inlay_hints.into_iter().map(|it| (it.range, it.label.to_string())).collect::<Vec<_>>();
+ assert_eq!(expected, actual, "\nExpected:\n{expected:#?}\n\nActual:\n{actual:#?}");
+ }
+
+ #[test]
+ fn fn_hints_ptr_rpit_fn_parentheses() {
+ check_types(
+ r#"
+//- minicore: fn, sized
+trait Trait {}
+
+fn foo1() -> *const impl Fn() { loop {} }
+fn foo2() -> *const (impl Fn() + Sized) { loop {} }
+fn foo3() -> *const (impl Fn() + ?Sized) { loop {} }
+fn foo4() -> *const (impl Sized + Fn()) { loop {} }
+fn foo5() -> *const (impl ?Sized + Fn()) { loop {} }
+fn foo6() -> *const (impl Fn() + Trait) { loop {} }
+fn foo7() -> *const (impl Fn() + Sized + Trait) { loop {} }
+fn foo8() -> *const (impl Fn() + ?Sized + Trait) { loop {} }
+fn foo9() -> *const (impl Fn() -> u8 + ?Sized) { loop {} }
+fn foo10() -> *const (impl Fn() + Sized + ?Sized) { loop {} }
+
+fn main() {
+ let foo = foo1();
+ // ^^^ *const impl Fn()
+ let foo = foo2();
+ // ^^^ *const impl Fn()
+ let foo = foo3();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo4();
+ // ^^^ *const impl Fn()
+ let foo = foo5();
+ // ^^^ *const (impl Fn() + ?Sized)
+ let foo = foo6();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo7();
+ // ^^^ *const (impl Fn() + Trait)
+ let foo = foo8();
+ // ^^^ *const (impl Fn() + Trait + ?Sized)
+ let foo = foo9();
+ // ^^^ *const (impl Fn() -> u8 + ?Sized)
+ let foo = foo10();
+ // ^^^ *const impl Fn()
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn unit_structs_have_no_type_hints() {
+ check_types(
+ r#"
+//- minicore: result
+struct SyntheticSyntax;
+
+fn main() {
+ match Ok(()) {
+ Ok(_) => (),
+ Err(SyntheticSyntax) => (),
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn let_statement() {
+ check_types(
+ r#"
+#[derive(PartialEq)]
+enum Option<T> { None, Some(T) }
+
+#[derive(PartialEq)]
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ struct InnerStruct {}
+
+ let test = 54;
+ //^^^^ i32
+ let test: i32 = 33;
+ let mut test = 33;
+ //^^^^ i32
+ let _ = 22;
+ let test = "test";
+ //^^^^ &str
+ let test = InnerStruct {};
+ //^^^^ InnerStruct
+
+ let test = unresolved();
+
+ let test = (42, 'a');
+ //^^^^ (i32, char)
+ let (a, (b, (c,)) = (2, (3, (9.2,));
+ //^ i32 ^ i32 ^ f64
+ let &x = &92;
+ //^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn if_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ if let None = &test {};
+ if let test = &test {};
+ //^^^^ &Option<Test>
+ if let Some(test) = &test {};
+ //^^^^ &Test
+ if let Some(Test { a, b }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: x, b: y }) = &test {};
+ //^ &Option<u32> ^ &u8
+ if let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+ if let Some(Test { a: None, b: y }) = &test {};
+ //^ &u8
+ if let Some(Test { b: y, .. }) = &test {};
+ //^ &u8
+ if test == None {}
+}"#,
+ );
+ }
+
+ #[test]
+ fn while_expr() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ let test = Some(Test { a: Some(3), b: 1 });
+ //^^^^ Option<Test>
+ while let Some(Test { a: Some(x), b: y }) = &test {};
+ //^ &u32 ^ &u8
+}"#,
+ );
+ }
+
+ #[test]
+ fn match_arm_list() {
+ check_types(
+ r#"
+//- minicore: option
+struct Test { a: Option<u32>, b: u8 }
+
+fn main() {
+ match Some(Test { a: Some(3), b: 1 }) {
+ None => (),
+ test => (),
+ //^^^^ Option<Test>
+ Some(Test { a: Some(x), b: y }) => (),
+ //^ u32 ^ u8
+ _ => {}
+ }
+}"#,
+ );
+ }
+
+ #[test]
+ fn complete_for_hint() {
+ check_types(
+ r#"
+//- minicore: iterator
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+ pub fn push(&mut self, t: T) {}
+}
+
+impl<T> IntoIterator for Vec<T> {
+ type Item = T;
+ type IntoIter = IntoIter<T>;
+}
+
+struct IntoIter<T> {}
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = T;
+}
+
+fn main() {
+ let mut data = Vec::new();
+ //^^^^ Vec<&str>
+ data.push("foo");
+ for i in data {
+ //^ &str
+ let z = i;
+ //^ &str
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multi_dyn_trait_bounds() {
+ check_types(
+ r#"
+pub struct Vec<T> {}
+
+impl<T> Vec<T> {
+ pub fn new() -> Self { Vec {} }
+}
+
+pub struct Box<T> {}
+
+trait Display {}
+auto trait Sync {}
+
+fn main() {
+ // The block expression wrapping disables the constructor hint hiding logic
+ let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<&(dyn Display + Sync)>>
+ let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
+ //^^ Vec<Box<*const (dyn Display + Sync)>>
+ let _v = { Vec::<Box<dyn Display + Sync>>::new() };
+ //^^ Vec<Box<dyn Display + Sync>>
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_hints() {
+ check_types(
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter;
+ //^^ MyIter
+ let _x = iter::repeat(0);
+ //^^ impl Iterator<Item = i32>
+ fn generic<T: Clone>(t: T) {
+ let _x = iter::repeat(t);
+ //^^ impl Iterator<Item = T>
+ let _chained = iter::repeat(t).take(10);
+ //^^^^^^^^ impl Iterator<Item = T>
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn skip_constructor_and_enum_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_named_constructor_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: try, option
+use core::ops::ControlFlow;
+
+mod x {
+ pub mod y { pub struct Foo; }
+ pub struct Foo;
+ pub enum AnotherEnum {
+ Variant()
+ };
+}
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+enum Enum {
+ Variant(u32)
+}
+
+fn times2(value: i32) -> i32 {
+ 2 * value
+}
+
+fn main() {
+ let enumb = Enum::Variant(0);
+
+ let strukt = x::Foo;
+ let strukt = x::y::Foo;
+ let strukt = Struct;
+ let strukt = Struct::new();
+
+ let tuple_struct = TupleStruct();
+
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic(0);
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = Generic::<i32>::new();
+ let generic3 = <Generic<i32>>::new();
+ let generic4 = Generic::<i32>(0);
+
+
+ let option = Some(0);
+ // ^^^^^^ Option<i32>
+ let func = times2;
+ // ^^^^ fn times2(i32) -> i32
+ let closure = |x: i32| x * 2;
+ // ^^^^^^^ |i32| -> i32
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn shows_constructor_type_hints_when_enabled() {
+ check_types(
+ r#"
+//- minicore: try
+use core::ops::ControlFlow;
+
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+ fn new() -> Self {
+ Struct
+ }
+ fn try_new() -> ControlFlow<(), Self> {
+ ControlFlow::Continue(Struct)
+ }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+ fn new() -> Self {
+ Generic(0)
+ }
+}
+
+fn main() {
+ let strukt = Struct::new();
+ // ^^^^^^ Struct
+ let tuple_struct = TupleStruct();
+ // ^^^^^^^^^^^^ TupleStruct
+ let generic0 = Generic::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic1 = Generic::<i32>::new();
+ // ^^^^^^^^ Generic<i32>
+ let generic2 = <Generic<i32>>::new();
+ // ^^^^^^^^ Generic<i32>
+}
+
+fn fallible() -> ControlFlow<()> {
+ let strukt = Struct::try_new()?;
+ // ^^^^^^ Struct
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn closures() {
+ check(
+ r#"
+fn main() {
+ let mut start = 0;
+ //^^^^^ i32
+ (0..2).for_each(|increment | { start += increment; });
+ //^^^^^^^^^ i32
+
+ let multiply =
+ //^^^^^^^^ |i32, i32| -> i32
+ | a, b| a * b
+ //^ i32 ^ i32
+
+ ;
+
+ let _: i32 = multiply(1, 2);
+ //^ a ^ b
+ let multiply_ref = &multiply;
+ //^^^^^^^^^^^^ &|i32, i32| -> i32
+
+ let return_42 = || 42;
+ //^^^^^^^^^ || -> i32
+ || { 42 };
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn return_type_hints_for_closure_without_block() {
+ check_with_config(
+ InlayHintsConfig {
+ closure_return_type_hints: ClosureReturnTypeHints::Always,
+ ..DISABLED_CONFIG
+ },
+ r#"
+fn main() {
+ let a = || { 0 };
+ //^^ i32
+ let b = || 0;
+ //^^ i32
+}"#,
+ );
+ }
+
+ #[test]
+ fn skip_closure_type_hints() {
+ check_with_config(
+ InlayHintsConfig {
+ type_hints: true,
+ hide_closure_initialization_hints: true,
+ ..DISABLED_CONFIG
+ },
+ r#"
+//- minicore: fn
+fn main() {
+ let multiple_2 = |x: i32| { x * 2 };
+
+ let multiple_2 = |x: i32| x * 2;
+ // ^^^^^^^^^^ |i32| -> i32
+
+ let (not) = (|x: bool| { !x });
+ // ^^^ |bool| -> bool
+
+ let (is_zero, _b) = (|x: usize| { x == 0 }, false);
+ // ^^^^^^^ |usize| -> bool
+ // ^^ bool
+
+ let plus_one = |x| { x + 1 };
+ // ^ u8
+ foo(plus_one);
+
+ let add_mul = bar(|x: u8| { x + 1 });
+ // ^^^^^^^ impl FnOnce(u8) -> u8 + ?Sized
+
+ let closure = if let Some(6) = add_mul(2).checked_sub(1) {
+ // ^^^^^^^ fn(i32) -> i32
+ |x: i32| { x * 2 }
+ } else {
+ |x: i32| { x * 3 }
+ };
+}
+
+fn foo(f: impl FnOnce(u8) -> u8) {}
+
+fn bar(f: impl FnOnce(u8) -> u8) -> impl FnOnce(u8) -> u8 {
+ move |x: u8| f(x) * 2
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hint_truncation() {
+ check_with_config(
+ InlayHintsConfig { max_length: Some(8), ..TEST_CONFIG },
+ r#"
+struct Smol<T>(T);
+
+struct VeryLongOuterName<T>(T);
+
+fn main() {
+ let a = Smol(0u32);
+ //^ Smol<u32>
+ let b = VeryLongOuterName(0usize);
+ //^ VeryLongOuterName<…>
+ let c = Smol(Smol(0u32))
+ //^ Smol<Smol<…>>
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs
new file mode 100644
index 000000000..a0166d004
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs
@@ -0,0 +1,142 @@
+//! Implementation of "binding mode" inlay hints:
+//! ```no_run
+//! let /* & */ (/* ref */ x,) = &(0,);
+//! ```
+use hir::{Mutability, Semantics};
+use ide_db::RootDatabase;
+
+use syntax::ast::{self, AstNode};
+
+use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ pat: &ast::Pat,
+) -> Option<()> {
+ if !config.binding_mode_hints {
+ return None;
+ }
+
+ let outer_paren_pat = pat
+ .syntax()
+ .ancestors()
+ .skip(1)
+ .map_while(ast::Pat::cast)
+ .map_while(|pat| match pat {
+ ast::Pat::ParenPat(pat) => Some(pat),
+ _ => None,
+ })
+ .last();
+ let range =
+ outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range();
+ let pattern_adjustments = sema.pattern_adjustments(pat);
+ pattern_adjustments.iter().for_each(|ty| {
+ let reference = ty.is_reference();
+ let mut_reference = ty.is_mutable_reference();
+ let r = match (reference, mut_reference) {
+ (true, true) => "&mut",
+ (true, false) => "&",
+ _ => return,
+ };
+ acc.push(InlayHint {
+ range,
+ kind: InlayKind::BindingModeHint,
+ label: r.to_string().into(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ });
+ match pat {
+ ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
+ let bm = sema.binding_mode_of_pat(pat)?;
+ let bm = match bm {
+ hir::BindingMode::Move => return None,
+ hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
+ hir::BindingMode::Ref(Mutability::Shared) => "ref",
+ };
+ acc.push(InlayHint {
+ range: pat.syntax().text_range(),
+ kind: InlayKind::BindingModeHint,
+ label: bm.to_string().into(),
+ tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
+ });
+ }
+ ast::Pat::OrPat(pat) if !pattern_adjustments.is_empty() && outer_paren_pat.is_none() => {
+ acc.push(InlayHint {
+ range: pat.syntax().text_range(),
+ kind: InlayKind::OpeningParenthesis,
+ label: "(".into(),
+ tooltip: None,
+ });
+ acc.push(InlayHint {
+ range: pat.syntax().text_range(),
+ kind: InlayKind::ClosingParenthesis,
+ label: ")".into(),
+ tooltip: None,
+ });
+ }
+ _ => (),
+ }
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+ InlayHintsConfig,
+ };
+
+ #[test]
+ fn hints_binding_modes() {
+ check_with_config(
+ InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
+ r#"
+fn __(
+ (x,): (u32,),
+ (x,): &(u32,),
+ //^^^^&
+ //^ ref
+ (x,): &mut (u32,)
+ //^^^^&mut
+ //^ ref mut
+) {
+ let (x,) = (0,);
+ let (x,) = &(0,);
+ //^^^^ &
+ //^ ref
+ let (x,) = &mut (0,);
+ //^^^^ &mut
+ //^ ref mut
+ let &mut (x,) = &mut (0,);
+ let (ref mut x,) = &mut (0,);
+ //^^^^^^^^^^^^ &mut
+ let &mut (ref mut x,) = &mut (0,);
+ let (mut x,) = &mut (0,);
+ //^^^^^^^^ &mut
+ match (0,) {
+ (x,) => ()
+ }
+ match &(0,) {
+ (x,) | (x,) => (),
+ //^^^^^^^^^^^&
+ //^ ref
+ //^ ref
+ //^^^^^^^^^^^(
+ //^^^^^^^^^^^)
+ ((x,) | (x,)) => (),
+ //^^^^^^^^^^^^^&
+ //^ ref
+ //^ ref
+ }
+ match &mut (0,) {
+ (x,) => ()
+ //^^^^ &mut
+ //^ ref mut
+ }
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
new file mode 100644
index 000000000..8810d5d34
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
@@ -0,0 +1,665 @@
+//! Implementation of "chaining" inlay hints.
+use ide_db::famous_defs::FamousDefs;
+use syntax::{
+ ast::{self, AstNode},
+ Direction, NodeOrToken, SyntaxKind, T,
+};
+
+use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+
+use super::label_of_ty;
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ expr: &ast::Expr,
+) -> Option<()> {
+ if !config.chaining_hints {
+ return None;
+ }
+
+ if matches!(expr, ast::Expr::RecordExpr(_)) {
+ return None;
+ }
+
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let desc_expr = descended.as_ref().unwrap_or(expr);
+
+ let mut tokens = expr
+ .syntax()
+ .siblings_with_tokens(Direction::Next)
+ .filter_map(NodeOrToken::into_token)
+ .filter(|t| match t.kind() {
+ SyntaxKind::WHITESPACE if !t.text().contains('\n') => false,
+ SyntaxKind::COMMENT => false,
+ _ => true,
+ });
+
+ // Chaining can be defined as an expression whose next sibling tokens are newline and dot
+ // Ignoring extra whitespace and comments
+ let next = tokens.next()?.kind();
+ if next == SyntaxKind::WHITESPACE {
+ let mut next_next = tokens.next()?.kind();
+ while next_next == SyntaxKind::WHITESPACE {
+ next_next = tokens.next()?.kind();
+ }
+ if next_next == T![.] {
+ let ty = sema.type_of_expr(desc_expr)?.original;
+ if ty.is_unknown() {
+ return None;
+ }
+ if matches!(expr, ast::Expr::PathExpr(_)) {
+ if let Some(hir::Adt::Struct(st)) = ty.as_adt() {
+ if st.fields(sema.db).is_empty() {
+ return None;
+ }
+ }
+ }
+ acc.push(InlayHint {
+ range: expr.syntax().text_range(),
+ kind: InlayKind::ChainingHint,
+ label: label_of_ty(famous_defs, config, ty)?,
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
+ });
+ }
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use crate::{
+ inlay_hints::tests::{
+ check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS,
+ TEST_CONFIG,
+ },
+ InlayHintsConfig,
+ };
+
+ #[track_caller]
+ fn check_chains(ra_fixture: &str) {
+ check_with_config(InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG }, ra_fixture);
+ }
+
+ #[test]
+ fn chaining_hints_ignore_comments() {
+ check_expect(
+ InlayHintsConfig {
+ type_hints: false,
+ chaining_hints: true,
+ ..DISABLED_CONFIG_WITH_LINKS
+ },
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C))
+ .into_b() // This is a comment
+ // This is another comment
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 147..172,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "B",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 63..64,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..172,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 147..154,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "A",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 7..8,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 147..154,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn chaining_hints_without_newlines() {
+ check_chains(
+ r#"
+struct A(B);
+impl A { fn into_b(self) -> B { self.0 } }
+struct B(C);
+impl B { fn into_c(self) -> C { self.0 } }
+struct C;
+
+fn main() {
+ let c = A(B(C)).into_b().into_c();
+}"#,
+ );
+ }
+
+ #[test]
+ fn disabled_location_links() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
+ r#"
+ struct A { pub b: B }
+ struct B { pub c: C }
+ struct C(pub bool);
+ struct D;
+
+ impl D {
+ fn foo(&self) -> i32 { 42 }
+ }
+
+ fn main() {
+ let x = A { b: B { c: C(true) } }
+ .b
+ .c
+ .0;
+ let x = D
+ .foo();
+ }"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 143..190,
+ kind: ChainingHint,
+ label: [
+ "C",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..190,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 143..179,
+ kind: ChainingHint,
+ label: [
+ "B",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..179,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn struct_access_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ r#"
+struct A { pub b: B }
+struct B { pub c: C }
+struct C(pub bool);
+struct D;
+
+impl D {
+ fn foo(&self) -> i32 { 42 }
+}
+
+fn main() {
+ let x = A { b: B { c: C(true) } }
+ .b
+ .c
+ .0;
+ let x = D
+ .foo();
+}"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 143..190,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "C",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 51..52,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..190,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 143..179,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "B",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 29..30,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 143..179,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn generic_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ r#"
+struct A<T>(T);
+struct B<T>(T);
+struct C<T>(T);
+struct X<T,R>(T, R);
+
+impl<T> A<T> {
+ fn new(t: T) -> Self { A(t) }
+ fn into_b(self) -> B<T> { B(self.0) }
+}
+impl<T> B<T> {
+ fn into_c(self) -> C<T> { C(self.0) }
+}
+fn main() {
+ let c = A::new(X(42, true))
+ .into_b()
+ .into_c();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 246..283,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "B",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 23..24,
+ },
+ ),
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "X",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 55..56,
+ },
+ ),
+ },
+ "<i32, bool>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..283,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 246..265,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "A",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 7..8,
+ },
+ ),
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "X",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 55..56,
+ },
+ ),
+ },
+ "<i32, bool>>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 246..265,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn shorten_iterator_chaining_hints() {
+ check_expect(
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ r#"
+//- minicore: iterators
+use core::iter;
+
+struct MyIter;
+
+impl Iterator for MyIter {
+ type Item = ();
+ fn next(&mut self) -> Option<Self::Item> {
+ None
+ }
+}
+
+fn main() {
+ let _x = MyIter.by_ref()
+ .take(5)
+ .by_ref()
+ .take(5)
+ .by_ref();
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 174..241,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..241,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..224,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..224,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..206,
+ kind: ChainingHint,
+ label: [
+ "impl Iterator<Item = ()>",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..206,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 174..189,
+ kind: ChainingHint,
+ label: [
+ "&mut ",
+ InlayHintLabelPart {
+ text: "MyIter",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 24..30,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 174..189,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
+ fn hints_in_attr_call() {
+ check_expect(
+ TEST_CONFIG,
+ r#"
+//- proc_macros: identity, input_replace
+struct Struct;
+impl Struct {
+ fn chain(self) -> Self {
+ self
+ }
+}
+#[proc_macros::identity]
+fn main() {
+ let strukt = Struct;
+ strukt
+ .chain()
+ .chain()
+ .chain();
+ Struct::chain(strukt);
+}
+"#,
+ expect![[r#"
+ [
+ InlayHint {
+ range: 124..130,
+ kind: TypeHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "Struct",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 7..13,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 124..130,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..185,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "Struct",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 7..13,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..185,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 145..168,
+ kind: ChainingHint,
+ label: [
+ "",
+ InlayHintLabelPart {
+ text: "Struct",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 7..13,
+ },
+ ),
+ },
+ "",
+ ],
+ tooltip: Some(
+ HoverRanged(
+ FileId(
+ 0,
+ ),
+ 145..168,
+ ),
+ ),
+ },
+ InlayHint {
+ range: 222..228,
+ kind: ParameterHint,
+ label: [
+ "self",
+ ],
+ tooltip: Some(
+ HoverOffset(
+ FileId(
+ 0,
+ ),
+ 42,
+ ),
+ ),
+ },
+ ]
+ "#]],
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs
new file mode 100644
index 000000000..e340c64c5
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs
@@ -0,0 +1,196 @@
+//! Implementation of "closing brace" inlay hints:
+//! ```no_run
+//! fn g() {
+//! } /* fn g */
+//! ```
+use hir::{HirDisplay, Semantics};
+use ide_db::{base_db::FileRange, RootDatabase};
+use syntax::{
+ ast::{self, AstNode, HasName},
+ match_ast, SyntaxKind, SyntaxNode, T,
+};
+
+use crate::{
+ inlay_hints::InlayHintLabelPart, FileId, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
+};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ node: SyntaxNode,
+) -> Option<()> {
+ let min_lines = config.closing_brace_hints_min_lines?;
+
+ let name = |it: ast::Name| it.syntax().text_range();
+
+ let mut closing_token;
+ let (label, name_range) = if let Some(item_list) = ast::AssocItemList::cast(node.clone()) {
+ closing_token = item_list.r_curly_token()?;
+
+ let parent = item_list.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Impl(imp) => {
+ let imp = sema.to_def(&imp)?;
+ let ty = imp.self_ty(sema.db);
+ let trait_ = imp.trait_(sema.db);
+ let hint_text = match trait_ {
+ Some(tr) => format!("impl {} for {}", tr.name(sema.db), ty.display_truncated(sema.db, config.max_length)),
+ None => format!("impl {}", ty.display_truncated(sema.db, config.max_length)),
+ };
+ (hint_text, None)
+ },
+ ast::Trait(tr) => {
+ (format!("trait {}", tr.name()?), tr.name().map(name))
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(list) = ast::ItemList::cast(node.clone()) {
+ closing_token = list.r_curly_token()?;
+
+ let module = ast::Module::cast(list.syntax().parent()?)?;
+ (format!("mod {}", module.name()?), module.name().map(name))
+ } else if let Some(block) = ast::BlockExpr::cast(node.clone()) {
+ closing_token = block.stmt_list()?.r_curly_token()?;
+
+ let parent = block.syntax().parent()?;
+ match_ast! {
+ match parent {
+ ast::Fn(it) => {
+ // FIXME: this could include parameters, but `HirDisplay` prints too much info
+ // and doesn't respect the max length either, so the hints end up way too long
+ (format!("fn {}", it.name()?), it.name().map(name))
+ },
+ ast::Static(it) => (format!("static {}", it.name()?), it.name().map(name)),
+ ast::Const(it) => {
+ if it.underscore_token().is_some() {
+ ("const _".into(), None)
+ } else {
+ (format!("const {}", it.name()?), it.name().map(name))
+ }
+ },
+ _ => return None,
+ }
+ }
+ } else if let Some(mac) = ast::MacroCall::cast(node.clone()) {
+ let last_token = mac.syntax().last_token()?;
+ if last_token.kind() != T![;] && last_token.kind() != SyntaxKind::R_CURLY {
+ return None;
+ }
+ closing_token = last_token;
+
+ (
+ format!("{}!", mac.path()?),
+ mac.path().and_then(|it| it.segment()).map(|it| it.syntax().text_range()),
+ )
+ } else {
+ return None;
+ };
+
+ if let Some(mut next) = closing_token.next_token() {
+ if next.kind() == T![;] {
+ if let Some(tok) = next.next_token() {
+ closing_token = next;
+ next = tok;
+ }
+ }
+ if !(next.kind() == SyntaxKind::WHITESPACE && next.text().contains('\n')) {
+ // Only display the hint if the `}` is the last token on the line
+ return None;
+ }
+ }
+
+ let mut lines = 1;
+ node.text().for_each_chunk(|s| lines += s.matches('\n').count());
+ if lines < min_lines {
+ return None;
+ }
+
+ let linked_location = config
+ .location_links
+ .then(|| name_range.map(|range| FileRange { file_id, range }))
+ .flatten();
+ acc.push(InlayHint {
+ range: closing_token.text_range(),
+ kind: InlayKind::ClosingBraceHint,
+ label: InlayHintLabel { parts: vec![InlayHintLabelPart { text: label, linked_location }] },
+ tooltip: None, // provided by label part location
+ });
+
+ None
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+ InlayHintsConfig,
+ };
+
+ #[test]
+ fn hints_closing_brace() {
+ check_with_config(
+ InlayHintsConfig { closing_brace_hints_min_lines: Some(2), ..DISABLED_CONFIG },
+ r#"
+fn a() {}
+
+fn f() {
+} // no hint unless `}` is the last token on the line
+
+fn g() {
+ }
+//^ fn g
+
+fn h<T>(with: T, arguments: u8, ...) {
+ }
+//^ fn h
+
+trait Tr {
+ fn f();
+ fn g() {
+ }
+ //^ fn g
+ }
+//^ trait Tr
+impl Tr for () {
+ }
+//^ impl Tr for ()
+impl dyn Tr {
+ }
+//^ impl dyn Tr
+
+static S0: () = 0;
+static S1: () = {};
+static S2: () = {
+ };
+//^ static S2
+const _: () = {
+ };
+//^ const _
+
+mod m {
+ }
+//^ mod m
+
+m! {}
+m!();
+m!(
+ );
+//^ m!
+
+m! {
+ }
+//^ m!
+
+fn f() {
+ let v = vec![
+ ];
+ }
+//^ fn f
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs
new file mode 100644
index 000000000..d9929beaa
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs
@@ -0,0 +1,49 @@
+//! Implementation of "closure return type" inlay hints.
+use ide_db::{base_db::FileId, famous_defs::FamousDefs};
+use syntax::ast::{self, AstNode};
+
+use crate::{
+ inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig,
+ InlayKind, InlayTooltip,
+};
+
+use super::label_of_ty;
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ file_id: FileId,
+ closure: ast::ClosureExpr,
+) -> Option<()> {
+ if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
+ return None;
+ }
+
+ if closure.ret_type().is_some() {
+ return None;
+ }
+
+ if !closure_has_block_body(&closure)
+ && config.closure_return_type_hints == ClosureReturnTypeHints::WithBlock
+ {
+ return None;
+ }
+
+ let param_list = closure.param_list()?;
+
+ let closure = sema.descend_node_into_attributes(closure).pop()?;
+ let ty = sema.type_of_expr(&ast::Expr::ClosureExpr(closure))?.adjusted();
+ let callable = ty.as_callable(sema.db)?;
+ let ty = callable.return_type();
+ if ty.is_unit() {
+ return None;
+ }
+ acc.push(InlayHint {
+ range: param_list.syntax().text_range(),
+ kind: InlayKind::ClosureReturnTypeHint,
+ label: label_of_ty(famous_defs, config, ty)?,
+ tooltip: Some(InlayTooltip::HoverRanged(file_id, param_list.syntax().text_range())),
+ });
+ Some(())
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
new file mode 100644
index 000000000..f32c4bdf2
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
@@ -0,0 +1,142 @@
+//! Implementation of "enum variant discriminant" inlay hints:
+//! ```no_run
+//! enum Foo {
+//! Bar/* = 0*/,
+//! }
+//! ```
+use ide_db::{base_db::FileId, famous_defs::FamousDefs};
+use syntax::ast::{self, AstNode, HasName};
+
+use crate::{DiscriminantHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ FamousDefs(sema, _): &FamousDefs<'_, '_>,
+ config: &InlayHintsConfig,
+ _: FileId,
+ variant: &ast::Variant,
+) -> Option<()> {
+ let field_list = match config.discriminant_hints {
+ DiscriminantHints::Always => variant.field_list(),
+ DiscriminantHints::Fieldless => match variant.field_list() {
+ Some(_) => return None,
+ None => None,
+ },
+ DiscriminantHints::Never => return None,
+ };
+
+ if variant.eq_token().is_some() {
+ return None;
+ }
+
+ let name = variant.name()?;
+
+ let descended = sema.descend_node_into_attributes(variant.clone()).pop();
+ let desc_pat = descended.as_ref().unwrap_or(variant);
+ let v = sema.to_def(desc_pat)?;
+ let d = v.eval(sema.db);
+
+ acc.push(InlayHint {
+ range: match field_list {
+ Some(field_list) => name.syntax().text_range().cover(field_list.syntax().text_range()),
+ None => name.syntax().text_range(),
+ },
+ kind: InlayKind::DiscriminantHint,
+ label: match &d {
+ Ok(v) => format!("{}", v).into(),
+ Err(_) => "?".into(),
+ },
+ tooltip: Some(InlayTooltip::String(match &d {
+ Ok(_) => "enum variant discriminant".into(),
+ Err(e) => format!("{e:?}").into(),
+ })),
+ });
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::inlay_hints::{
+ tests::{check_with_config, DISABLED_CONFIG},
+ DiscriminantHints, InlayHintsConfig,
+ };
+
+ #[track_caller]
+ fn check_discriminants(ra_fixture: &str) {
+ check_with_config(
+ InlayHintsConfig { discriminant_hints: DiscriminantHints::Always, ..DISABLED_CONFIG },
+ ra_fixture,
+ );
+ }
+
+ #[track_caller]
+ fn check_discriminants_fieldless(ra_fixture: &str) {
+ check_with_config(
+ InlayHintsConfig {
+ discriminant_hints: DiscriminantHints::Fieldless,
+ ..DISABLED_CONFIG
+ },
+ ra_fixture,
+ );
+ }
+
+ #[test]
+ fn fieldless() {
+ check_discriminants(
+ r#"
+enum Enum {
+ Variant,
+ //^^^^^^^0
+ Variant1,
+ //^^^^^^^^1
+ Variant2,
+ //^^^^^^^^2
+ Variant5 = 5,
+ Variant6,
+ //^^^^^^^^6
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn datacarrying_mixed() {
+ check_discriminants(
+ r#"
+enum Enum {
+ Variant(),
+ //^^^^^^^^^0
+ Variant1,
+ //^^^^^^^^1
+ Variant2 {},
+ //^^^^^^^^^^^2
+ Variant3,
+ //^^^^^^^^3
+ Variant5 = 5,
+ Variant6,
+ //^^^^^^^^6
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn datacarrying_mixed_fieldless_set() {
+ check_discriminants_fieldless(
+ r#"
+enum Enum {
+ Variant(),
+ Variant1,
+ //^^^^^^^^1
+ Variant2 {},
+ Variant3,
+ //^^^^^^^^3
+ Variant5 = 5,
+ Variant6,
+ //^^^^^^^^6
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
new file mode 100644
index 000000000..2aa5e3dc7
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs
@@ -0,0 +1,325 @@
+//! Implementation of "lifetime elision" inlay hints:
+//! ```no_run
+//! fn example/* <'0> */(a: &/* '0 */()) {}
+//! ```
+use ide_db::{syntax_helpers::node_ext::walk_ty, FxHashMap};
+use itertools::Itertools;
+use syntax::SmolStr;
+use syntax::{
+ ast::{self, AstNode, HasGenericParams, HasName},
+ SyntaxToken,
+};
+
+use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ func: ast::Fn,
+) -> Option<()> {
+ if config.lifetime_elision_hints == LifetimeElisionHints::Never {
+ return None;
+ }
+
+ let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: label.into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
+ };
+
+ let param_list = func.param_list()?;
+ let generic_param_list = func.generic_param_list();
+ let ret_type = func.ret_type();
+ let self_param = param_list.self_param().filter(|it| it.amp_token().is_some());
+
+ let is_elided = |lt: &Option<ast::Lifetime>| match lt {
+ Some(lt) => matches!(lt.text().as_str(), "'_"),
+ None => true,
+ };
+
+ let potential_lt_refs = {
+ let mut acc: Vec<_> = vec![];
+ if let Some(self_param) = &self_param {
+ let lifetime = self_param.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((None, self_param.amp_token(), lifetime, is_elided));
+ }
+ param_list.params().filter_map(|it| Some((it.pat(), it.ty()?))).for_each(|(pat, ty)| {
+ // FIXME: check path types
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(r) => {
+ let lifetime = r.lifetime();
+ let is_elided = is_elided(&lifetime);
+ acc.push((
+ pat.as_ref().and_then(|it| match it {
+ ast::Pat::IdentPat(p) => p.name(),
+ _ => None,
+ }),
+ r.amp_token(),
+ lifetime,
+ is_elided,
+ ));
+ false
+ }
+ ast::Type::FnPtrType(_) => true,
+ ast::Type::PathType(t) => {
+ t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some()
+ }
+ _ => false,
+ })
+ });
+ acc
+ };
+
+ // allocate names
+ let mut gen_idx_name = {
+ let mut gen = (0u8..).map(|idx| match idx {
+ idx if idx < 10 => SmolStr::from_iter(['\'', (idx + 48) as char]),
+ idx => format!("'{idx}").into(),
+ });
+ move || gen.next().unwrap_or_default()
+ };
+ let mut allocated_lifetimes = vec![];
+
+ let mut used_names: FxHashMap<SmolStr, usize> =
+ match config.param_names_for_lifetime_elision_hints {
+ true => generic_param_list
+ .iter()
+ .flat_map(|gpl| gpl.lifetime_params())
+ .filter_map(|param| param.lifetime())
+ .filter_map(|lt| Some((SmolStr::from(lt.text().as_str().get(1..)?), 0)))
+ .collect(),
+ false => Default::default(),
+ };
+ {
+ let mut potential_lt_refs = potential_lt_refs.iter().filter(|&&(.., is_elided)| is_elided);
+ if let Some(_) = &self_param {
+ if let Some(_) = potential_lt_refs.next() {
+ allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints {
+ // self can't be used as a lifetime, so no need to check for collisions
+ "'self".into()
+ } else {
+ gen_idx_name()
+ });
+ }
+ }
+ potential_lt_refs.for_each(|(name, ..)| {
+ let name = match name {
+ Some(it) if config.param_names_for_lifetime_elision_hints => {
+ if let Some(c) = used_names.get_mut(it.text().as_str()) {
+ *c += 1;
+ SmolStr::from(format!("'{text}{c}", text = it.text().as_str()))
+ } else {
+ used_names.insert(it.text().as_str().into(), 0);
+ SmolStr::from_iter(["\'", it.text().as_str()])
+ }
+ }
+ _ => gen_idx_name(),
+ };
+ allocated_lifetimes.push(name);
+ });
+ }
+
+ // fetch output lifetime if elision rule applies
+ let output = match potential_lt_refs.as_slice() {
+ [(_, _, lifetime, _), ..] if self_param.is_some() || potential_lt_refs.len() == 1 => {
+ match lifetime {
+ Some(lt) => match lt.text().as_str() {
+ "'_" => allocated_lifetimes.get(0).cloned(),
+ "'static" => None,
+ name => Some(name.into()),
+ },
+ None => allocated_lifetimes.get(0).cloned(),
+ }
+ }
+ [..] => None,
+ };
+
+ if allocated_lifetimes.is_empty() && output.is_none() {
+ return None;
+ }
+
+ // apply hints
+ // apply output if required
+ let mut is_trivial = true;
+ if let (Some(output_lt), Some(r)) = (&output, ret_type) {
+ if let Some(ty) = r.ty() {
+ walk_ty(&ty, &mut |ty| match ty {
+ ast::Type::RefType(ty) if ty.lifetime().is_none() => {
+ if let Some(amp) = ty.amp_token() {
+ is_trivial = false;
+ acc.push(mk_lt_hint(amp, output_lt.to_string()));
+ }
+ false
+ }
+ ast::Type::FnPtrType(_) => true,
+ ast::Type::PathType(t) => {
+ t.path().and_then(|it| it.segment()).and_then(|it| it.param_list()).is_some()
+ }
+ _ => false,
+ })
+ }
+ }
+
+ if config.lifetime_elision_hints == LifetimeElisionHints::SkipTrivial && is_trivial {
+ return None;
+ }
+
+ let mut a = allocated_lifetimes.iter();
+ for (_, amp_token, _, is_elided) in potential_lt_refs {
+ if is_elided {
+ let t = amp_token?;
+ let lt = a.next()?;
+ acc.push(mk_lt_hint(t, lt.to_string()));
+ }
+ }
+
+ // generate generic param list things
+ match (generic_param_list, allocated_lifetimes.as_slice()) {
+ (_, []) => (),
+ (Some(gpl), allocated_lifetimes) => {
+ let angle_tok = gpl.l_angle_token()?;
+ let is_empty = gpl.generic_params().next().is_none();
+ acc.push(InlayHint {
+ range: angle_tok.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: format!(
+ "{}{}",
+ allocated_lifetimes.iter().format(", "),
+ if is_empty { "" } else { ", " }
+ )
+ .into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ });
+ }
+ (None, allocated_lifetimes) => acc.push(InlayHint {
+ range: func.name()?.syntax().text_range(),
+ kind: InlayKind::GenericParamListHint,
+ label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
+ tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
+ }),
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check, check_with_config, TEST_CONFIG},
+ InlayHintsConfig, LifetimeElisionHints,
+ };
+
+ #[test]
+ fn hints_lifetimes() {
+ check(
+ r#"
+fn empty() {}
+
+fn no_gpl(a: &()) {}
+ //^^^^^^<'0>
+ // ^'0
+fn empty_gpl<>(a: &()) {}
+ // ^'0 ^'0
+fn partial<'b>(a: &(), b: &'b ()) {}
+// ^'0, $ ^'0
+fn partial<'a>(a: &'a (), b: &()) {}
+// ^'0, $ ^'0
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+// ^^^^^^^^<'0, '1>
+ // ^'0 ^'1
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+// ^^^^^^^^^<'0, '1, '2>
+ //^'0 ^'1 ^'2
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ // ^^^<'0>
+ // ^'0
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_named() {
+ check_with_config(
+ InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },
+ r#"
+fn nested_in<'named>(named: & &X< &()>) {}
+// ^'named1, 'named2, 'named3, $
+ //^'named1 ^'named2 ^'named3
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_trivial_skip() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::SkipTrivial,
+ ..TEST_CONFIG
+ },
+ r#"
+fn no_gpl(a: &()) {}
+fn empty_gpl<>(a: &()) {}
+fn partial<'b>(a: &(), b: &'b ()) {}
+fn partial<'a>(a: &'a (), b: &()) {}
+
+fn single_ret(a: &()) -> &() {}
+// ^^^^^^^^^^<'0>
+ // ^'0 ^'0
+fn full_mul(a: &(), b: &()) {}
+
+fn foo<'c>(a: &'c ()) -> &() {}
+ // ^'c
+
+fn nested_in(a: & &X< &()>) {}
+fn nested_out(a: &()) -> & &X< &()>{}
+// ^^^^^^^^^^<'0>
+ //^'0 ^'0 ^'0 ^'0
+
+impl () {
+ fn foo(&self) {}
+ fn foo(&self) -> &() {}
+ // ^^^<'0>
+ // ^'0 ^'0
+ fn foo(&self, a: &()) -> &() {}
+ // ^^^<'0, '1>
+ // ^'0 ^'1 ^'0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn hints_lifetimes_skip_fn_likes() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..TEST_CONFIG
+ },
+ r#"
+fn fn_ptr(a: fn(&()) -> &()) {}
+fn fn_trait<>(a: impl Fn(&()) -> &()) {}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs
new file mode 100644
index 000000000..588a0e3b6
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs
@@ -0,0 +1,75 @@
+//! Implementation of "implicit static" inlay hints:
+//! ```no_run
+//! static S: &/* 'static */str = "";
+//! ```
+use either::Either;
+use syntax::{
+ ast::{self, AstNode},
+ SyntaxKind,
+};
+
+use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ config: &InlayHintsConfig,
+ statik_or_const: Either<ast::Static, ast::Const>,
+) -> Option<()> {
+ if config.lifetime_elision_hints != LifetimeElisionHints::Always {
+ return None;
+ }
+
+ if let Either::Right(it) = &statik_or_const {
+ if ast::AssocItemList::can_cast(
+ it.syntax().parent().map_or(SyntaxKind::EOF, |it| it.kind()),
+ ) {
+ return None;
+ }
+ }
+
+ if let Some(ast::Type::RefType(ty)) = statik_or_const.either(|it| it.ty(), |it| it.ty()) {
+ if ty.lifetime().is_none() {
+ let t = ty.amp_token()?;
+ acc.push(InlayHint {
+ range: t.text_range(),
+ kind: InlayKind::LifetimeHint,
+ label: "'static".to_owned().into(),
+ tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
+ });
+ }
+ }
+
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, TEST_CONFIG},
+ InlayHintsConfig, LifetimeElisionHints,
+ };
+
+ #[test]
+ fn hints_lifetimes_static() {
+ check_with_config(
+ InlayHintsConfig {
+ lifetime_elision_hints: LifetimeElisionHints::Always,
+ ..TEST_CONFIG
+ },
+ r#"
+trait Trait {}
+static S: &str = "";
+// ^'static
+const C: &str = "";
+// ^'static
+const C: &dyn Trait = panic!();
+// ^'static
+
+impl () {
+ const C: &str = "";
+ const C: &dyn Trait = panic!();
+}
+"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs
new file mode 100644
index 000000000..ecee67632
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs
@@ -0,0 +1,546 @@
+//! Implementation of "param name" inlay hints:
+//! ```no_run
+//! fn max(x: i32, y: i32) -> i32 { x + y }
+//! _ = max(/*x*/4, /*y*/4);
+//! ```
+use either::Either;
+use hir::{Callable, Semantics};
+use ide_db::{base_db::FileRange, RootDatabase};
+
+use stdx::to_lower_snake_case;
+use syntax::ast::{self, AstNode, HasArgList, HasName, UnaryOp};
+
+use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+
+pub(super) fn hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ config: &InlayHintsConfig,
+ expr: ast::Expr,
+) -> Option<()> {
+ if !config.parameter_hints {
+ return None;
+ }
+
+ let (callable, arg_list) = get_callable(sema, &expr)?;
+ let hints = callable
+ .params(sema.db)
+ .into_iter()
+ .zip(arg_list.args())
+ .filter_map(|((param, _ty), arg)| {
+ // Only annotate hints for expressions that exist in the original file
+ let range = sema.original_range_opt(arg.syntax())?;
+ let (param_name, name_syntax) = match param.as_ref()? {
+ Either::Left(pat) => ("self".to_string(), pat.name()),
+ Either::Right(pat) => match pat {
+ ast::Pat::IdentPat(it) => (it.name()?.to_string(), it.name()),
+ _ => return None,
+ },
+ };
+ Some((name_syntax, param_name, arg, range))
+ })
+ .filter(|(_, param_name, arg, _)| {
+ !should_hide_param_name_hint(sema, &callable, param_name, arg)
+ })
+ .map(|(param, param_name, _, FileRange { range, .. })| {
+ let mut tooltip = None;
+ if let Some(name) = param {
+ if let hir::CallableKind::Function(f) = callable.kind() {
+ // assert the file is cached so we can map out of macros
+ if let Some(_) = sema.source(f) {
+ tooltip = sema.original_range_opt(name.syntax());
+ }
+ }
+ }
+
+ InlayHint {
+ range,
+ kind: InlayKind::ParameterHint,
+ label: param_name.into(),
+ tooltip: tooltip.map(|it| InlayTooltip::HoverOffset(it.file_id, it.range.start())),
+ }
+ });
+
+ acc.extend(hints);
+ Some(())
+}
+
+fn get_callable(
+ sema: &Semantics<'_, RootDatabase>,
+ expr: &ast::Expr,
+) -> Option<(hir::Callable, ast::ArgList)> {
+ match expr {
+ ast::Expr::CallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.type_of_expr(&expr.expr()?)?.original.as_callable(sema.db).zip(expr.arg_list())
+ }
+ ast::Expr::MethodCallExpr(expr) => {
+ let descended = sema.descend_node_into_attributes(expr.clone()).pop();
+ let expr = descended.as_ref().unwrap_or(expr);
+ sema.resolve_method_call_as_callable(expr).zip(expr.arg_list())
+ }
+ _ => None,
+ }
+}
+
+fn should_hide_param_name_hint(
+ sema: &Semantics<'_, RootDatabase>,
+ callable: &hir::Callable,
+ param_name: &str,
+ argument: &ast::Expr,
+) -> bool {
+ // These are to be tested in the `parameter_hint_heuristics` test
+ // hide when:
+ // - the parameter name is a suffix of the function's name
+ // - the argument is a qualified constructing or call expression where the qualifier is an ADT
+ // - exact argument<->parameter match(ignoring leading underscore) or parameter is a prefix/suffix
+ // of argument with _ splitting it off
+ // - param starts with `ra_fixture`
+ // - param is a well known name in a unary function
+
+ let param_name = param_name.trim_start_matches('_');
+ if param_name.is_empty() {
+ return true;
+ }
+
+ if matches!(argument, ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(UnaryOp::Not)) {
+ return false;
+ }
+
+ let fn_name = match callable.kind() {
+ hir::CallableKind::Function(it) => Some(it.name(sema.db).to_smol_str()),
+ _ => None,
+ };
+ let fn_name = fn_name.as_deref();
+ is_param_name_suffix_of_fn_name(param_name, callable, fn_name)
+ || is_argument_similar_to_param_name(argument, param_name)
+ || param_name.starts_with("ra_fixture")
+ || (callable.n_params() == 1 && is_obvious_param(param_name))
+ || is_adt_constructor_similar_to_param_name(sema, argument, param_name)
+}
+
+/// Hide the parameter name of a unary function if it is a `_` - prefixed suffix of the function's name, or equal.
+///
+/// `fn strip_suffix(suffix)` will be hidden.
+/// `fn stripsuffix(suffix)` will not be hidden.
+fn is_param_name_suffix_of_fn_name(
+ param_name: &str,
+ callable: &Callable,
+ fn_name: Option<&str>,
+) -> bool {
+ match (callable.n_params(), fn_name) {
+ (1, Some(function)) => {
+ function == param_name
+ || function
+ .len()
+ .checked_sub(param_name.len())
+ .and_then(|at| function.is_char_boundary(at).then(|| function.split_at(at)))
+ .map_or(false, |(prefix, suffix)| {
+ suffix.eq_ignore_ascii_case(param_name) && prefix.ends_with('_')
+ })
+ }
+ _ => false,
+ }
+}
+
+fn is_argument_similar_to_param_name(argument: &ast::Expr, param_name: &str) -> bool {
+ // check whether param_name and argument are the same or
+ // whether param_name is a prefix/suffix of argument(split at `_`)
+ let argument = match get_string_representation(argument) {
+ Some(argument) => argument,
+ None => return false,
+ };
+
+ // std is honestly too panic happy...
+ let str_split_at = |str: &str, at| str.is_char_boundary(at).then(|| argument.split_at(at));
+
+ let param_name = param_name.trim_start_matches('_');
+ let argument = argument.trim_start_matches('_');
+
+ match str_split_at(argument, param_name.len()) {
+ Some((prefix, rest)) if prefix.eq_ignore_ascii_case(param_name) => {
+ return rest.is_empty() || rest.starts_with('_');
+ }
+ _ => (),
+ }
+ match argument.len().checked_sub(param_name.len()).and_then(|at| str_split_at(argument, at)) {
+ Some((rest, suffix)) if param_name.eq_ignore_ascii_case(suffix) => {
+ return rest.is_empty() || rest.ends_with('_');
+ }
+ _ => (),
+ }
+ false
+}
+
+fn get_string_representation(expr: &ast::Expr) -> Option<String> {
+ match expr {
+ ast::Expr::MethodCallExpr(method_call_expr) => {
+ let name_ref = method_call_expr.name_ref()?;
+ match name_ref.text().as_str() {
+ "clone" | "as_ref" => method_call_expr.receiver().map(|rec| rec.to_string()),
+ name_ref => Some(name_ref.to_owned()),
+ }
+ }
+ ast::Expr::MacroExpr(macro_expr) => {
+ Some(macro_expr.macro_call()?.path()?.segment()?.to_string())
+ }
+ ast::Expr::FieldExpr(field_expr) => Some(field_expr.name_ref()?.to_string()),
+ ast::Expr::PathExpr(path_expr) => Some(path_expr.path()?.segment()?.to_string()),
+ ast::Expr::PrefixExpr(prefix_expr) => get_string_representation(&prefix_expr.expr()?),
+ ast::Expr::RefExpr(ref_expr) => get_string_representation(&ref_expr.expr()?),
+ ast::Expr::CastExpr(cast_expr) => get_string_representation(&cast_expr.expr()?),
+ _ => None,
+ }
+}
+
+fn is_obvious_param(param_name: &str) -> bool {
+ // avoid displaying hints for common functions like map, filter, etc.
+ // or other obvious words used in std
+ let is_obvious_param_name =
+ matches!(param_name, "predicate" | "value" | "pat" | "rhs" | "other");
+ param_name.len() == 1 || is_obvious_param_name
+}
+
+fn is_adt_constructor_similar_to_param_name(
+ sema: &Semantics<'_, RootDatabase>,
+ argument: &ast::Expr,
+ param_name: &str,
+) -> bool {
+ let path = match argument {
+ ast::Expr::CallExpr(c) => c.expr().and_then(|e| match e {
+ ast::Expr::PathExpr(p) => p.path(),
+ _ => None,
+ }),
+ ast::Expr::PathExpr(p) => p.path(),
+ ast::Expr::RecordExpr(r) => r.path(),
+ _ => return false,
+ };
+ let path = match path {
+ Some(it) => it,
+ None => return false,
+ };
+ (|| match sema.resolve_path(&path)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name)
+ }
+ hir::PathResolution::Def(hir::ModuleDef::Function(_) | hir::ModuleDef::Variant(_)) => {
+ if to_lower_snake_case(&path.segment()?.name_ref()?.text()) == param_name {
+ return Some(true);
+ }
+ let qual = path.qualifier()?;
+ match sema.resolve_path(&qual)? {
+ hir::PathResolution::Def(hir::ModuleDef::Adt(_)) => {
+ Some(to_lower_snake_case(&qual.segment()?.name_ref()?.text()) == param_name)
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ })()
+ .unwrap_or(false)
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{
+ inlay_hints::tests::{check_with_config, DISABLED_CONFIG},
+ InlayHintsConfig,
+ };
+
+ #[track_caller]
+ fn check_params(ra_fixture: &str) {
+ check_with_config(
+ InlayHintsConfig { parameter_hints: true, ..DISABLED_CONFIG },
+ ra_fixture,
+ );
+ }
+
+ #[test]
+ fn param_hints_only() {
+ check_params(
+ r#"
+fn foo(a: i32, b: i32) -> i32 { a + b }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ a
+ 4,
+ //^ b
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_on_closure() {
+ check_params(
+ r#"
+fn main() {
+ let clo = |a: u8, b: u8| a + b;
+ clo(
+ 1,
+ //^ a
+ 2,
+ //^ b
+ );
+}
+ "#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name_still_hints() {
+ check_params(
+ r#"
+fn max(x: i32, y: i32) -> i32 { x + y }
+fn main() {
+ let _x = max(
+ 4,
+ //^ x
+ 4,
+ //^ y
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_similar_to_fn_name() {
+ check_params(
+ r#"
+fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ check_params(
+ r#"
+fn param_with_underscore(underscore: i32) -> i32 { underscore }
+fn main() {
+ let _x = param_with_underscore(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_name_same_as_fn_name() {
+ check_params(
+ r#"
+fn foo(foo: i32) -> i32 { foo }
+fn main() {
+ let _x = foo(
+ 4,
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn never_hide_param_when_multiple_params() {
+ check_params(
+ r#"
+fn foo(foo: i32, bar: i32) -> i32 { bar + baz }
+fn main() {
+ let _x = foo(
+ 4,
+ //^ foo
+ 8,
+ //^ bar
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn param_hints_look_through_as_ref_and_clone() {
+ check_params(
+ r#"
+fn foo(bar: i32, baz: f32) {}
+
+fn main() {
+ let bar = 3;
+ let baz = &"baz";
+ let fez = 1.0;
+ foo(bar.clone(), bar.clone());
+ //^^^^^^^^^^^ baz
+ foo(bar.as_ref(), bar.as_ref());
+ //^^^^^^^^^^^^ baz
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn self_param_hints() {
+ check_params(
+ r#"
+struct Foo;
+
+impl Foo {
+ fn foo(self: Self) {}
+ fn bar(self: &Self) {}
+}
+
+fn main() {
+ Foo::foo(Foo);
+ //^^^ self
+ Foo::bar(&Foo);
+ //^^^^ self
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn param_name_hints_show_for_literals() {
+ check_params(
+ r#"pub fn test(a: i32, b: i32) -> [i32; 2] { [a, b] }
+fn main() {
+ test(
+ 0xa_b,
+ //^^^^^ a
+ 0xa_b,
+ //^^^^^ b
+ );
+}"#,
+ )
+ }
+
+ #[test]
+ fn function_call_parameter_hint() {
+ check_params(
+ r#"
+//- minicore: option
+struct FileId {}
+struct SmolStr {}
+
+struct TextRange {}
+struct SyntaxKind {}
+struct NavigationTarget {}
+
+struct Test {}
+
+impl Test {
+ fn method(&self, mut param: i32) -> i32 { param * 2 }
+
+ fn from_syntax(
+ file_id: FileId,
+ name: SmolStr,
+ focus_range: Option<TextRange>,
+ full_range: TextRange,
+ kind: SyntaxKind,
+ docs: Option<String>,
+ ) -> NavigationTarget {
+ NavigationTarget {}
+ }
+}
+
+fn test_func(mut foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
+ foo + bar
+}
+
+fn main() {
+ let not_literal = 1;
+ let _: i32 = test_func(1, 2, "hello", 3, not_literal);
+ //^ foo ^ bar ^^^^^^^ msg ^^^^^^^^^^^ last
+ let t: Test = Test {};
+ t.method(123);
+ //^^^ param
+ Test::method(&t, 3456);
+ //^^ self ^^^^ param
+ Test::from_syntax(
+ FileId {},
+ "impl".into(),
+ //^^^^^^^^^^^^^ name
+ None,
+ //^^^^ focus_range
+ TextRange {},
+ //^^^^^^^^^^^^ full_range
+ SyntaxKind {},
+ //^^^^^^^^^^^^^ kind
+ None,
+ //^^^^ docs
+ );
+}"#,
+ );
+ }
+
+ #[test]
+ fn parameter_hint_heuristics() {
+ check_params(
+ r#"
+fn check(ra_fixture_thing: &str) {}
+
+fn map(f: i32) {}
+fn filter(predicate: i32) {}
+
+fn strip_suffix(suffix: &str) {}
+fn stripsuffix(suffix: &str) {}
+fn same(same: u32) {}
+fn same2(_same2: u32) {}
+
+fn enum_matches_param_name(completion_kind: CompletionKind) {}
+
+fn foo(param: u32) {}
+fn bar(param_eter: u32) {}
+
+enum CompletionKind {
+ Keyword,
+}
+
+fn non_ident_pat((a, b): (u32, u32)) {}
+
+fn main() {
+ const PARAM: u32 = 0;
+ foo(PARAM);
+ foo(!PARAM);
+ // ^^^^^^ param
+ check("");
+
+ map(0);
+ filter(0);
+
+ strip_suffix("");
+ stripsuffix("");
+ //^^ suffix
+ same(0);
+ same2(0);
+
+ enum_matches_param_name(CompletionKind::Keyword);
+
+ let param = 0;
+ foo(param);
+ foo(param as _);
+ let param_end = 0;
+ foo(param_end);
+ let start_param = 0;
+ foo(start_param);
+ let param2 = 0;
+ foo(param2);
+ //^^^^^^ param
+
+ macro_rules! param {
+ () => {};
+ };
+ foo(param!());
+
+ let param_eter = 0;
+ bar(param_eter);
+ let param_eter_end = 0;
+ bar(param_eter_end);
+ let start_param_eter = 0;
+ bar(start_param_eter);
+ let param_eter2 = 0;
+ bar(param_eter2);
+ //^^^^^^^^^^^ param_eter
+
+ non_ident_pat((0, 0));
+}"#,
+ );
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index 7402e86f3..239456cb2 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -81,8 +81,8 @@ pub use crate::{
highlight_related::{HighlightRelatedConfig, HighlightedRange},
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
inlay_hints::{
- AdjustmentHints, ClosureReturnTypeHints, InlayHint, InlayHintLabel, InlayHintsConfig,
- InlayKind, InlayTooltip, LifetimeElisionHints,
+ AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint,
+ InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
},
join_lines::JoinLinesConfig,
markup::Markup,
@@ -236,6 +236,7 @@ impl Analysis {
Ok(Vec::new()),
false,
CrateOrigin::CratesIo { repo: None, name: None },
+ None,
);
change.change_file(file_id, Some(Arc::new(text)));
change.set_crate_graph(crate_graph);
diff --git a/src/tools/rust-analyzer/crates/ide/src/markup.rs b/src/tools/rust-analyzer/crates/ide/src/markup.rs
index 60c193c40..de9fef61a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/markup.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/markup.rs
@@ -33,6 +33,6 @@ impl Markup {
self.text.as_str()
}
pub fn fenced_block(contents: &impl fmt::Display) -> Markup {
- format!("```rust\n{}\n```", contents).into()
+ format!("```rust\n{contents}\n```").into()
}
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/moniker.rs b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
index fcbf6d8e5..af5e96d23 100644
--- a/src/tools/rust-analyzer/crates/ide/src/moniker.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/moniker.rs
@@ -273,7 +273,7 @@ mod tests {
fn no_moniker(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
if let Some(x) = analysis.moniker(position).unwrap() {
- assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {:?}", x);
+ assert_eq!(x.info.len(), 0, "Moniker founded but no moniker expected: {x:?}");
}
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
index 9f049e298..3aa799d43 100644
--- a/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/navigation_target.rs
@@ -117,10 +117,10 @@ impl NavigationTarget {
self.full_range
);
if let Some(focus_range) = self.focus_range {
- buf.push_str(&format!(" {:?}", focus_range))
+ buf.push_str(&format!(" {focus_range:?}"))
}
if let Some(container_name) = &self.container_name {
- buf.push_str(&format!(" {}", container_name))
+ buf.push_str(&format!(" {container_name}"))
}
buf
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
index b4df04370..15bdf14fb 100644
--- a/src/tools/rust-analyzer/crates/ide/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -345,7 +345,7 @@ mod tests {
let (analysis, position) = fixture::position(ra_fixture_before);
let rename_result = analysis
.rename(position, new_name)
- .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
+ .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
match rename_result {
Ok(source_change) => {
let mut text_edit_builder = TextEdit::builder();
@@ -364,14 +364,11 @@ mod tests {
}
Err(err) => {
if ra_fixture_after.starts_with("error:") {
- let error_message = ra_fixture_after
- .chars()
- .into_iter()
- .skip("error:".len())
- .collect::<String>();
+ let error_message =
+ ra_fixture_after.chars().skip("error:".len()).collect::<String>();
assert_eq!(error_message.trim(), err.to_string());
} else {
- panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
+ panic!("Rename to '{new_name}' failed unexpectedly: {err}")
}
}
};
@@ -397,11 +394,11 @@ mod tests {
let (analysis, position) = fixture::position(ra_fixture);
let result = analysis
.prepare_rename(position)
- .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err));
+ .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));
match result {
Ok(RangeInfo { range, info: () }) => {
let source = analysis.file_text(position.file_id).unwrap();
- expect.assert_eq(&format!("{:?}: {}", range, &source[range]))
+ expect.assert_eq(&format!("{range:?}: {}", &source[range]))
}
Err(RenameError(err)) => expect.assert_eq(&err),
};
diff --git a/src/tools/rust-analyzer/crates/ide/src/runnables.rs b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
index 0181c6b8e..5b35262aa 100644
--- a/src/tools/rust-analyzer/crates/ide/src/runnables.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/runnables.rs
@@ -66,12 +66,12 @@ impl Runnable {
// test package::module::testname
pub fn label(&self, target: Option<String>) -> String {
match &self.kind {
- RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
- RunnableKind::TestMod { path } => format!("test-mod {}", path),
- RunnableKind::Bench { test_id } => format!("bench {}", test_id),
- RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
+ RunnableKind::Test { test_id, .. } => format!("test {test_id}"),
+ RunnableKind::TestMod { path } => format!("test-mod {path}"),
+ RunnableKind::Bench { test_id } => format!("bench {test_id}"),
+ RunnableKind::DocTest { test_id, .. } => format!("doctest {test_id}"),
RunnableKind::Bin => {
- target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
+ target.map_or_else(|| "run binary".to_string(), |t| format!("run {t}"))
}
}
}
@@ -377,7 +377,7 @@ pub(crate) fn runnable_impl(
} else {
String::new()
};
- let mut test_id = format!("{}{}", adt_name, params);
+ let mut test_id = format!("{adt_name}{params}");
test_id.retain(|c| c != ' ');
let test_id = TestId::Path(test_id);
diff --git a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs
index 2d8662764..ae539a5d3 100644
--- a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs
@@ -36,6 +36,7 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
data.proc_macro.clone(),
data.is_proc_macro,
data.origin.clone(),
+ data.target_layout.clone(),
);
map.insert(old_id, new_id);
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
index e7412d27f..f807ba30f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
@@ -74,20 +74,28 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
ast::ArgList(arg_list) => {
let cursor_outside = arg_list.r_paren_token().as_ref() == Some(&token);
if cursor_outside {
- return None;
+ continue;
}
- return signature_help_for_call(&sema, token);
+ return signature_help_for_call(&sema, arg_list, token);
},
ast::GenericArgList(garg_list) => {
let cursor_outside = garg_list.r_angle_token().as_ref() == Some(&token);
if cursor_outside {
- return None;
+ continue;
}
- return signature_help_for_generics(&sema, token);
+ return signature_help_for_generics(&sema, garg_list, token);
},
_ => (),
}
}
+
+ // Stop at multi-line expressions, since the signature of the outer call is not very
+ // helpful inside them.
+ if let Some(expr) = ast::Expr::cast(node.clone()) {
+ if expr.syntax().text().contains_char('\n') {
+ return None;
+ }
+ }
}
None
@@ -95,10 +103,11 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
fn signature_help_for_call(
sema: &Semantics<'_, RootDatabase>,
+ arg_list: ast::ArgList,
token: SyntaxToken,
) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef
- let mut node = token.parent()?;
+ let mut node = arg_list.syntax().parent()?;
let calling_node = loop {
if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
if callable
@@ -109,14 +118,6 @@ fn signature_help_for_call(
}
}
- // Stop at multi-line expressions, since the signature of the outer call is not very
- // helpful inside them.
- if let Some(expr) = ast::Expr::cast(node.clone()) {
- if expr.syntax().text().contains_char('\n') {
- return None;
- }
- }
-
node = node.parent()?;
};
@@ -200,10 +201,11 @@ fn signature_help_for_call(
fn signature_help_for_generics(
sema: &Semantics<'_, RootDatabase>,
+ garg_list: ast::GenericArgList,
token: SyntaxToken,
) -> Option<SignatureHelp> {
- let parent = token.parent()?;
- let arg_list = parent
+ let arg_list = garg_list
+ .syntax()
.ancestors()
.filter_map(ast::GenericArgList::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
@@ -644,7 +646,7 @@ pub fn add_one(x: i32) -> i32 {
x + 1
}
-pub fn do() {
+pub fn r#do() {
add_one($0
}"#,
expect![[r##"
@@ -770,6 +772,32 @@ fn f() {
"#,
expect![[]],
);
+ check(
+ r#"
+fn foo(a: u8) -> u8 {a}
+fn bar(a: u8) -> u8 {a}
+fn f() {
+ foo(bar(123)$0)
+}
+"#,
+ expect![[r#"
+ fn foo(a: u8) -> u8
+ ^^^^^
+ "#]],
+ );
+ check(
+ r#"
+struct Vec<T>(T);
+struct Vec2<T>(T);
+fn f() {
+ let _: Vec2<Vec<u8>$0>
+}
+"#,
+ expect![[r#"
+ struct Vec2<T>
+ ^
+ "#]],
+ );
}
#[test]
diff --git a/src/tools/rust-analyzer/crates/ide/src/static_index.rs b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
index 2380cf738..a6b30ba13 100644
--- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
@@ -13,6 +13,7 @@ use syntax::{AstNode, SyntaxKind::*, SyntaxToken, TextRange, T};
use crate::{
hover::hover_for_definition,
+ inlay_hints::AdjustmentHintsMode,
moniker::{def_to_moniker, MonikerResult},
parent_module::crates_for,
Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
@@ -106,13 +107,17 @@ impl StaticIndex<'_> {
.analysis
.inlay_hints(
&InlayHintsConfig {
+ location_links: true,
render_colons: true,
+ discriminant_hints: crate::DiscriminantHints::Fieldless,
type_hints: true,
parameter_hints: true,
chaining_hints: true,
closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
lifetime_elision_hints: crate::LifetimeElisionHints::Never,
adjustment_hints: crate::AdjustmentHints::Never,
+ adjustment_hints_mode: AdjustmentHintsMode::Prefix,
+ adjustment_hints_hide_outside_unsafe: false,
hide_named_constructor_hints: false,
hide_closure_initialization_hints: false,
param_names_for_lifetime_elision_hints: false,
@@ -231,13 +236,13 @@ mod tests {
for (range, _) in f.tokens {
let x = FileRange { file_id: f.file_id, range };
if !range_set.contains(&x) {
- panic!("additional range {:?}", x);
+ panic!("additional range {x:?}");
}
range_set.remove(&x);
}
}
if !range_set.is_empty() {
- panic!("unfound ranges {:?}", range_set);
+ panic!("unfound ranges {range_set:?}");
}
}
@@ -252,13 +257,13 @@ mod tests {
continue;
}
if !range_set.contains(&x) {
- panic!("additional definition {:?}", x);
+ panic!("additional definition {x:?}");
}
range_set.remove(&x);
}
}
if !range_set.is_empty() {
- panic!("unfound definitions {:?}", range_set);
+ panic!("unfound definitions {range_set:?}");
}
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/status.rs b/src/tools/rust-analyzer/crates/ide/src/status.rs
index 20810c25b..7ce782f93 100644
--- a/src/tools/rust-analyzer/crates/ide/src/status.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/status.rs
@@ -52,8 +52,8 @@ pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
let crate_graph = db.crate_graph();
for krate in crates {
let display_crate = |krate: CrateId| match &crate_graph[krate].display_name {
- Some(it) => format!("{}({:?})", it, krate),
- None => format!("{:?}", krate),
+ Some(it) => format!("{it}({krate:?})"),
+ None => format!("{krate:?}"),
};
format_to!(buf, "Crate: {}\n", display_crate(krate));
let deps = crate_graph[krate]
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
index e7d0a8be7..892e6a9bb 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/highlight.rs
@@ -111,7 +111,7 @@ fn punctuation(
let is_raw_ptr = (|| {
let prefix_expr = parent.and_then(ast::PrefixExpr::cast)?;
let expr = prefix_expr.expr()?;
- sema.type_of_expr(&expr)?.original.is_raw_ptr().then(|| ())
+ sema.type_of_expr(&expr)?.original.is_raw_ptr().then_some(())
})();
if let Some(()) = is_raw_ptr {
HlTag::Operator(HlOperator::Other) | HlMod::Unsafe
@@ -174,6 +174,7 @@ fn keyword(
| T![return]
| T![while]
| T![yield] => h | HlMod::ControlFlow,
+ T![do] | T![yeet] if parent_matches::<ast::YeetExpr>(&token) => h | HlMod::ControlFlow,
T![for] if parent_matches::<ast::ForExpr>(&token) => h | HlMod::ControlFlow,
T![unsafe] => h | HlMod::Unsafe,
T![true] | T![false] => HlTag::BoolLiteral.into(),
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
index e91fd7f12..2c7823069 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/html.rs
@@ -52,7 +52,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
let class = r.highlight.to_string().replace('.', " ");
let color = match (rainbow, r.binding_hash) {
(true, Some(hash)) => {
- format!(" data-binding-hash=\"{}\" style=\"color: {};\"", hash, rainbowify(hash))
+ format!(" data-binding-hash=\"{hash}\" style=\"color: {};\"", rainbowify(hash))
}
_ => "".into(),
};
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
index 46cc667fc..2f870d769 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs
@@ -1028,6 +1028,26 @@ macro_rules! test {}
let _ = analysis.highlight(HL_CONFIG, file_id).unwrap();
}
+#[test]
+fn highlight_callable_no_crash() {
+ // regression test for #13838.
+ let (analysis, file_id) = fixture::file(
+ r#"
+//- minicore: fn, sized
+impl<A, F: ?Sized> FnOnce<A> for &F
+where
+ F: Fn<A>,
+{
+ type Output = F::Output;
+}
+
+trait Trait {}
+fn foo(x: &fn(&dyn Trait)) {}
+"#,
+ );
+ let _ = analysis.highlight(HL_CONFIG, file_id).unwrap();
+}
+
/// Highlights the code given by the `ra_fixture` argument, renders the
/// result as HTML, and compares it with the HTML file given as `snapshot`.
/// Note that the `snapshot` file is overwritten by the rendered HTML.
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
index 4256fea0f..bb6827e8a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_tree.rs
@@ -32,7 +32,7 @@ pub(crate) fn syntax_tree(
}
};
- format!("{:#?}", node)
+ format!("{node:#?}")
} else {
format!("{:#?}", parse.tree().syntax())
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs
index 9118f3c69..eba5a4856 100644
--- a/src/tools/rust-analyzer/crates/ide/src/typing.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs
@@ -397,7 +397,7 @@ mod tests {
fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) {
let actual = do_type_char(char_typed, ra_fixture_before)
- .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
+ .unwrap_or_else(|| panic!("typing `{char_typed}` did nothing"));
assert_eq_text!(ra_fixture_after, &actual);
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs
index 48c171327..298482f2a 100644
--- a/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/typing/on_enter.rs
@@ -108,7 +108,7 @@ fn on_enter_in_comment(
}
let indent = node_indent(file, comment.syntax())?;
- let inserted = format!("\n{}{} $0", indent, prefix);
+ let inserted = format!("\n{indent}{prefix} $0");
let delete = if remove_trailing_whitespace {
let trimmed_len = comment.text().trim_end().len() as u32;
let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len;
@@ -129,7 +129,7 @@ fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<Te
let indent = IndentLevel::from_node(block.syntax());
let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
- edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{}", indent))).ok()?;
+ edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{indent}"))).ok()?;
Some(edit)
}
@@ -140,11 +140,8 @@ fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) ->
let indent = IndentLevel::from_node(list.syntax());
let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
- edit.union(TextEdit::insert(
- list.r_curly_token()?.text_range().start(),
- format!("\n{}", indent),
- ))
- .ok()?;
+ edit.union(TextEdit::insert(list.r_curly_token()?.text_range().start(), format!("\n{indent}")))
+ .ok()?;
Some(edit)
}