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/Cargo.toml39
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/file_structure.rs29
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_definition.rs64
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/highlight_related.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover.rs181
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/render.rs163
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/hover/tests.rs248
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs191
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs135
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/bind_pat.rs137
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/binding_mode.rs55
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs323
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closing_brace.rs14
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/closure_ret.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs108
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/fn_lifetime_fn.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/implicit_static.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/inlay_hints/param_name.rs11
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/join_lines.rs6
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/lib.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs140
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/references.rs395
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/rename.rs202
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs22
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/signature_help.rs299
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/static_index.rs7
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs9
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/syntax_highlighting/tests.rs2
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/typing.rs57
30 files changed, 2020 insertions, 850 deletions
diff --git a/src/tools/rust-analyzer/crates/ide/Cargo.toml b/src/tools/rust-analyzer/crates/ide/Cargo.toml
index 73f202630..30e514e41 100644
--- a/src/tools/rust-analyzer/crates/ide/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/ide/Cargo.toml
@@ -2,9 +2,11 @@
name = "ide"
version = "0.0.0"
description = "TBD"
-license = "MIT OR Apache-2.0"
-edition = "2021"
-rust-version = "1.65"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
[lib]
doctest = false
@@ -20,28 +22,31 @@ pulldown-cmark-to-cmark = "10.0.4"
pulldown-cmark = { version = "0.9.1", default-features = false }
url = "2.3.1"
dot = "0.1.4"
+smallvec.workspace = true
-stdx = { path = "../stdx", version = "0.0.0" }
-syntax = { path = "../syntax", version = "0.0.0" }
-text-edit = { path = "../text-edit", version = "0.0.0" }
-ide-db = { path = "../ide-db", version = "0.0.0" }
-cfg = { path = "../cfg", version = "0.0.0" }
-profile = { path = "../profile", version = "0.0.0" }
-ide-assists = { path = "../ide-assists", version = "0.0.0" }
-ide-diagnostics = { path = "../ide-diagnostics", version = "0.0.0" }
-ide-ssr = { path = "../ide-ssr", version = "0.0.0" }
-ide-completion = { path = "../ide-completion", version = "0.0.0" }
-
+# local deps
+cfg.workspace = true
+ide-assists.workspace = true
+ide-completion.workspace = true
+ide-db.workspace = true
+ide-diagnostics.workspace = true
+ide-ssr.workspace = true
+profile.workspace = true
+stdx.workspace = true
+syntax.workspace = true
+text-edit.workspace = true
# ide should depend only on the top-level `hir` package. if you need
# something from some `hir-xxx` subpackage, reexport the API via `hir`.
-hir = { path = "../hir", version = "0.0.0" }
+hir.workspace = true
[target.'cfg(not(any(target_arch = "wasm32", target_os = "emscripten")))'.dependencies]
-toolchain = { path = "../toolchain", version = "0.0.0" }
+toolchain.workspace = true
[dev-dependencies]
-test-utils = { path = "../test-utils" }
expect-test = "1.4.0"
+# local deps
+test-utils.workspace = true
+
[features]
in-rust-tree = ["ide-assists/in-rust-tree", "ide-diagnostics/in-rust-tree"]
diff --git a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs
index 68fd0952b..b23763dce 100644
--- a/src/tools/rust-analyzer/crates/ide/src/file_structure.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/file_structure.rs
@@ -160,7 +160,11 @@ fn structure_node(node: &SyntaxNode) -> Option<StructureNode> {
let label = match target_trait {
None => format!("impl {}", target_type.syntax().text()),
Some(t) => {
- format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
+ format!("impl {}{} for {}",
+ it.excl_token().map(|x| x.to_string()).unwrap_or_default(),
+ t.syntax().text(),
+ target_type.syntax().text(),
+ )
}
};
@@ -214,6 +218,29 @@ mod tests {
}
#[test]
+ fn test_nagative_trait_bound() {
+ let txt = r#"impl !Unpin for Test {}"#;
+ check(
+ txt,
+ expect![[r#"
+ [
+ StructureNode {
+ parent: None,
+ label: "impl !Unpin for Test",
+ navigation_range: 16..20,
+ node_range: 0..23,
+ kind: SymbolKind(
+ Impl,
+ ),
+ detail: None,
+ deprecated: false,
+ },
+ ]
+ "#]],
+ );
+ }
+
+ #[test]
fn test_file_structure() {
check(
r#"
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 c7130a2a4..e70bc2ec5 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
@@ -17,6 +17,7 @@ use crate::{
// 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
+// - fields in patterns will navigate to the field declaration of the struct, union or variant
pub(crate) fn goto_declaration(
db: &RootDatabase,
position: FilePosition,
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 73fd518a9..93019527f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_definition.rs
@@ -1916,4 +1916,68 @@ fn main() {
"#,
)
}
+
+ #[test]
+ fn query_impls_in_nearest_block() {
+ check(
+ r#"
+struct S1;
+impl S1 {
+ fn e() -> () {}
+}
+fn f1() {
+ struct S1;
+ impl S1 {
+ fn e() -> () {}
+ //^
+ }
+ fn f2() {
+ fn f3() {
+ S1::e$0();
+ }
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+struct S1;
+impl S1 {
+ fn e() -> () {}
+}
+fn f1() {
+ struct S1;
+ impl S1 {
+ fn e() -> () {}
+ //^
+ }
+ fn f2() {
+ struct S2;
+ S1::e$0();
+ }
+}
+fn f12() {
+ struct S1;
+ impl S1 {
+ fn e() -> () {}
+ }
+}
+"#,
+ );
+
+ check(
+ r#"
+struct S1;
+impl S1 {
+ fn e() -> () {}
+ //^
+}
+fn f2() {
+ struct S2;
+ S1::e$0();
+}
+"#,
+ );
+ }
}
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 55f8779ee..c889eb930 100644
--- a/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/highlight_related.rs
@@ -1356,7 +1356,6 @@ fn main() {
r#"
trait Trait {
fn func(self) {}
- //^^^^
}
impl Trait for () {
@@ -1376,7 +1375,6 @@ fn main() {
r#"
trait Trait {
fn func(self) {}
- //^^^^
}
impl Trait for () {
diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs
index b214fa12a..5f2c61f5b 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs
@@ -15,10 +15,11 @@ use ide_db::{
FxIndexSet, RootDatabase,
};
use itertools::Itertools;
-use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, T};
+use syntax::{ast, AstNode, SyntaxKind::*, SyntaxNode, T};
use crate::{
doc_links::token_as_doc_comment,
+ markdown_remove::remove_markdown,
markup::Markup,
runnables::{runnable_fn, runnable_mod},
FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, TryToNav,
@@ -26,14 +27,9 @@ use crate::{
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HoverConfig {
pub links_in_hover: bool,
- pub documentation: Option<HoverDocFormat>,
+ pub documentation: bool,
pub keywords: bool,
-}
-
-impl HoverConfig {
- fn markdown(&self) -> bool {
- matches!(self.documentation, Some(HoverDocFormat::Markdown))
- }
+ pub format: HoverDocFormat,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -90,19 +86,38 @@ pub struct HoverResult {
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
pub(crate) fn hover(
db: &RootDatabase,
- FileRange { file_id, range }: FileRange,
+ frange @ FileRange { file_id, range }: FileRange,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
let sema = &hir::Semantics::new(db);
let file = sema.parse(file_id).syntax().clone();
+ let mut res = if range.is_empty() {
+ hover_simple(sema, FilePosition { file_id, offset: range.start() }, file, config)
+ } else {
+ hover_ranged(sema, frange, file, config)
+ }?;
- if !range.is_empty() {
- return hover_ranged(&file, range, sema, config);
+ if let HoverDocFormat::PlainText = config.format {
+ res.info.markup = remove_markdown(res.info.markup.as_str()).into();
}
- let offset = range.start();
+ Some(res)
+}
+fn hover_simple(
+ sema: &Semantics<'_, RootDatabase>,
+ FilePosition { file_id, offset }: FilePosition,
+ file: SyntaxNode,
+ config: &HoverConfig,
+) -> Option<RangeInfo<HoverResult>> {
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
- IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] | T![Self] => 4,
+ IDENT
+ | INT_NUMBER
+ | LIFETIME_IDENT
+ | T![self]
+ | T![super]
+ | T![crate]
+ | T![Self]
+ | T![_] => 4,
// index and prefix ops
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
kind if kind.is_keyword() => 2,
@@ -135,19 +150,18 @@ pub(crate) fn hover(
} else {
sema.descend_into_macros_with_same_text(original_token.clone())
};
+ let descended = || descended.iter();
- // try lint hover
- let result = descended
- .iter()
+ let result = descended()
+ // try lint hover
.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)
})
- // try item definitions
+ // try definitions
.or_else(|| {
- descended
- .iter()
+ descended()
.filter_map(|token| {
let node = token.parent()?;
let class = IdentClass::classify_token(sema, token)?;
@@ -168,10 +182,12 @@ pub(crate) fn hover(
})
})
// try keywords
- .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
- // try rest item hover
+ .or_else(|| descended().find_map(|token| render::keyword(sema, config, token)))
+ // try _ hovers
+ .or_else(|| descended().find_map(|token| render::underscore(sema, config, token)))
+ // try rest pattern hover
.or_else(|| {
- descended.iter().find_map(|token| {
+ descended().find_map(|token| {
if token.kind() != DOT2 {
return None;
}
@@ -185,60 +201,43 @@ pub(crate) fn hover(
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
+ // try () call hovers
.or_else(|| {
- descended.iter().find_map(|token| hover_type_fallback(sema, config, token, token))
- })
-}
+ descended().find_map(|token| {
+ if token.kind() != T!['('] && token.kind() != T![')'] {
+ return None;
+ }
+ let arg_list = token.parent().and_then(ast::ArgList::cast)?.syntax().parent()?;
+ let call_expr = syntax::match_ast! {
+ match arg_list {
+ ast::CallExpr(expr) => expr.into(),
+ ast::MethodCallExpr(expr) => expr.into(),
+ _ => return None,
+ }
+ };
+ render::type_info_of(sema, config, &Either::Left(call_expr))
+ })
+ });
-pub(crate) fn hover_for_definition(
- sema: &Semantics<'_, RootDatabase>,
- file_id: FileId,
- definition: Definition,
- node: &SyntaxNode,
- config: &HoverConfig,
-) -> Option<HoverResult> {
- let famous_defs = match &definition {
- Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
- _ => None,
- };
- render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
- HoverResult {
- markup: render::process_markup(sema.db, definition, &markup, config),
- actions: show_implementations_action(sema.db, definition)
- .into_iter()
- .chain(show_fn_references_action(sema.db, definition))
- .chain(runnable_action(sema, definition, file_id))
- .chain(goto_type_action_for_def(sema.db, definition))
- .collect(),
- }
+ result.map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
})
}
fn hover_ranged(
- file: &SyntaxNode,
- range: syntax::TextRange,
sema: &Semantics<'_, RootDatabase>,
+ FileRange { range, .. }: FileRange,
+ file: SyntaxNode,
config: &HoverConfig,
) -> Option<RangeInfo<HoverResult>> {
// FIXME: make this work in attributes
- let expr_or_pat = file.covering_element(range).ancestors().find_map(|it| {
- match_ast! {
- match it {
- ast::Expr(expr) => Some(Either::Left(expr)),
- ast::Pat(pat) => Some(Either::Right(pat)),
- _ => None,
- }
- }
- })?;
+ let expr_or_pat = file
+ .covering_element(range)
+ .ancestors()
+ .take_while(|it| ast::MacroCall::can_cast(it.kind()) || !ast::Item::can_cast(it.kind()))
+ .find_map(Either::<ast::Expr, ast::Pat>::cast)?;
let res = match &expr_or_pat {
Either::Left(ast::Expr::TryExpr(try_expr)) => render::try_expr(sema, config, try_expr),
Either::Left(ast::Expr::PrefixExpr(prefix_expr))
@@ -248,7 +247,7 @@ fn hover_ranged(
}
_ => None,
};
- let res = res.or_else(|| render::type_info(sema, config, &expr_or_pat));
+ let res = res.or_else(|| render::type_info_of(sema, config, &expr_or_pat));
res.map(|it| {
let range = match expr_or_pat {
Either::Left(it) => it.syntax().text_range(),
@@ -258,37 +257,31 @@ fn hover_ranged(
})
}
-fn hover_type_fallback(
+pub(crate) fn hover_for_definition(
sema: &Semantics<'_, RootDatabase>,
+ file_id: FileId,
+ definition: Definition,
+ node: &SyntaxNode,
config: &HoverConfig,
- token: &SyntaxToken,
- original_token: &SyntaxToken,
-) -> Option<RangeInfo<HoverResult>> {
- let node =
- token.parent_ancestors().take_while(|it| !ast::Item::can_cast(it.kind())).find(|n| {
- ast::Expr::can_cast(n.kind())
- || ast::Pat::can_cast(n.kind())
- || ast::Type::can_cast(n.kind())
- })?;
-
- let expr_or_pat = match_ast! {
- match node {
- ast::Expr(it) => Either::Left(it),
- ast::Pat(it) => Either::Right(it),
- // If this node is a MACRO_CALL, it means that `descend_into_macros_many` failed to resolve.
- // (e.g expanding a builtin macro). So we give up here.
- ast::MacroCall(_it) => return None,
- _ => return None,
- }
+) -> Option<HoverResult> {
+ let famous_defs = match &definition {
+ Definition::BuiltinType(_) => Some(FamousDefs(sema, sema.scope(node)?.krate())),
+ _ => None,
};
-
- let res = render::type_info(sema, config, &expr_or_pat)?;
-
- let range = sema
- .original_range_opt(&node)
- .map(|frange| frange.range)
- .unwrap_or_else(|| original_token.text_range());
- Some(RangeInfo::new(range, res))
+ render::definition(sema.db, definition, famous_defs.as_ref(), config).map(|markup| {
+ HoverResult {
+ markup: render::process_markup(sema.db, definition, &markup, config),
+ actions: [
+ show_implementations_action(sema.db, definition),
+ show_fn_references_action(sema.db, definition),
+ runnable_action(sema, definition, file_id),
+ goto_type_action_for_def(sema.db, definition),
+ ]
+ .into_iter()
+ .flatten()
+ .collect(),
+ }
+ })
}
fn show_implementations_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> {
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 47257f0bf..22611cfb8 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs
@@ -26,58 +26,24 @@ use syntax::{
use crate::{
doc_links::{remove_links, rewrite_links},
hover::walk_and_push_ty,
- markdown_remove::remove_markdown,
HoverAction, HoverConfig, HoverResult, Markup,
};
-pub(super) fn type_info(
+pub(super) fn type_info_of(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
expr_or_pat: &Either<ast::Expr, ast::Pat>,
) -> Option<HoverResult> {
let TypeInfo { original, adjusted } = match expr_or_pat {
Either::Left(expr) => sema.type_of_expr(expr)?,
Either::Right(pat) => sema.type_of_pat(pat)?,
};
-
- 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);
- }
- };
- walk_and_push_ty(sema.db, &original, &mut push_new_def);
-
- res.markup = if let Some(adjusted_ty) = adjusted {
- walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
- let original = original.display(sema.db).to_string();
- let adjusted = adjusted_ty.display(sema.db).to_string();
- let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
- format!(
- "{bt_start}Type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
- original,
- adjusted,
- apad = static_text_diff_len + adjusted.len().max(original.len()),
- opad = original.len(),
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
- )
- .into()
- } else {
- if config.markdown() {
- Markup::fenced_block(&original.display(sema.db))
- } else {
- original.display(sema.db).to_string().into()
- }
- };
- res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
- Some(res)
+ type_info(sema, _config, original, adjusted)
}
pub(super) fn try_expr(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
try_expr: &ast::TryExpr,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&try_expr.expr()?)?.original;
@@ -153,14 +119,12 @@ pub(super) fn try_expr(
let ppad = static_text_len_diff.min(0).abs() as usize;
res.markup = format!(
- "{bt_start}{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n{bt_end}",
+ "```text\n{} Type: {:>pad0$}\nPropagated as: {:>pad1$}\n```\n",
s,
inner_ty,
body_ty,
pad0 = ty_len_max + tpad,
pad1 = ty_len_max + ppad,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into();
Some(res)
@@ -168,7 +132,7 @@ pub(super) fn try_expr(
pub(super) fn deref_expr(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
deref_expr: &ast::PrefixExpr,
) -> Option<HoverResult> {
let inner_ty = sema.type_of_expr(&deref_expr.expr()?)?.original;
@@ -197,15 +161,13 @@ pub(super) fn deref_expr(
.max(adjusted.len() + coerced_len)
.max(inner.len() + deref_len);
format!(
- "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n{bt_end}",
+ "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
inner,
original,
adjusted,
ipad = max_len - deref_len,
apad = max_len - type_len,
opad = max_len - coerced_len,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
} else {
@@ -215,13 +177,11 @@ pub(super) fn deref_expr(
let deref_len = "Dereferenced from: ".len();
let max_len = (original.len() + type_len).max(inner.len() + deref_len);
format!(
- "{bt_start}Dereferenced from: {:>ipad$}\nTo type: {:>apad$}\n{bt_end}",
+ "```text\nDereferenced from: {:>ipad$}\nTo type: {:>apad$}\n```\n",
inner,
original,
ipad = max_len - deref_len,
apad = max_len - type_len,
- bt_start = if config.markdown() { "```text\n" } else { "" },
- bt_end = if config.markdown() { "```\n" } else { "" }
)
.into()
};
@@ -230,12 +190,54 @@ pub(super) fn deref_expr(
Some(res)
}
+pub(super) fn underscore(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ token: &SyntaxToken,
+) -> Option<HoverResult> {
+ if token.kind() != T![_] {
+ return None;
+ }
+ let parent = token.parent()?;
+ let _it = match_ast! {
+ match parent {
+ ast::InferType(it) => it,
+ ast::UnderscoreExpr(it) => return type_info_of(sema, config, &Either::Left(ast::Expr::UnderscoreExpr(it))),
+ ast::WildcardPat(it) => return type_info_of(sema, config, &Either::Right(ast::Pat::WildcardPat(it))),
+ _ => return None,
+ }
+ };
+ // let it = infer_type.syntax().parent()?;
+ // match_ast! {
+ // match it {
+ // ast::LetStmt(_it) => (),
+ // ast::Param(_it) => (),
+ // ast::RetType(_it) => (),
+ // ast::TypeArg(_it) => (),
+
+ // ast::CastExpr(_it) => (),
+ // ast::ParenType(_it) => (),
+ // ast::TupleType(_it) => (),
+ // ast::PtrType(_it) => (),
+ // ast::RefType(_it) => (),
+ // ast::ArrayType(_it) => (),
+ // ast::SliceType(_it) => (),
+ // ast::ForType(_it) => (),
+ // _ => return None,
+ // }
+ // }
+
+ // FIXME: https://github.com/rust-lang/rust-analyzer/issues/11762, this currently always returns Unknown
+ // type_info(sema, config, sema.resolve_type(&ast::Type::InferType(it))?, None)
+ None
+}
+
pub(super) fn keyword(
sema: &Semantics<'_, RootDatabase>,
config: &HoverConfig,
token: &SyntaxToken,
) -> Option<HoverResult> {
- if !token.kind().is_keyword() || !config.documentation.is_some() || !config.keywords {
+ if !token.kind().is_keyword() || !config.documentation || !config.keywords {
return None;
}
let parent = token.parent()?;
@@ -259,7 +261,7 @@ pub(super) fn keyword(
/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
pub(super) fn struct_rest_pat(
sema: &Semantics<'_, RootDatabase>,
- config: &HoverConfig,
+ _config: &HoverConfig,
pattern: &RecordPat,
) -> HoverResult {
let missing_fields = sema.record_pattern_missing_fields(pattern);
@@ -288,11 +290,7 @@ pub(super) fn struct_rest_pat(
// get rid of trailing comma
s.truncate(s.len() - 2);
- if config.markdown() {
- Markup::fenced_block(&s)
- } else {
- s.into()
- }
+ Markup::fenced_block(&s)
};
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
res
@@ -346,13 +344,8 @@ pub(super) fn process_markup(
config: &HoverConfig,
) -> Markup {
let markup = markup.as_str();
- let markup = if !config.markdown() {
- remove_markdown(markup)
- } else if config.links_in_hover {
- rewrite_links(db, markup, def)
- } else {
- remove_links(markup)
- };
+ let markup =
+ if config.links_in_hover { rewrite_links(db, markup, def) } else { remove_links(markup) };
Markup::from(markup)
}
@@ -465,8 +458,9 @@ pub(super) fn definition(
Definition::DeriveHelper(it) => (format!("derive_helper {}", it.name(db)), None),
};
- let docs = match config.documentation {
- Some(_) => docs.or_else(|| {
+ let docs = docs
+ .filter(|_| config.documentation)
+ .or_else(|| {
// docs are missing, for assoc items of trait impls try to fall back to the docs of the
// original item of the trait
let assoc = def.as_assoc_item(db)?;
@@ -474,13 +468,46 @@ pub(super) fn definition(
let name = Some(assoc.name(db)?);
let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
item.docs(db)
- }),
- None => None,
- };
- let docs = docs.filter(|_| config.documentation.is_some()).map(Into::into);
+ })
+ .map(Into::into);
markup(docs, label, mod_path)
}
+fn type_info(
+ sema: &Semantics<'_, RootDatabase>,
+ _config: &HoverConfig,
+ original: hir::Type,
+ adjusted: Option<hir::Type>,
+) -> Option<HoverResult> {
+ 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);
+ }
+ };
+ walk_and_push_ty(sema.db, &original, &mut push_new_def);
+
+ res.markup = if let Some(adjusted_ty) = adjusted {
+ walk_and_push_ty(sema.db, &adjusted_ty, &mut push_new_def);
+ let original = original.display(sema.db).to_string();
+ let adjusted = adjusted_ty.display(sema.db).to_string();
+ let static_text_diff_len = "Coerced to: ".len() - "Type: ".len();
+ format!(
+ "```text\nType: {:>apad$}\nCoerced to: {:>opad$}\n```\n",
+ original,
+ adjusted,
+ apad = static_text_diff_len + adjusted.len().max(original.len()),
+ opad = original.len(),
+ )
+ .into()
+ } else {
+ Markup::fenced_block(&original.display(sema.db))
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ Some(res)
+}
+
fn render_builtin_attr(db: &RootDatabase, attr: hir::BuiltinAttr) -> Option<Markup> {
let name = attr.name(db);
let desc = format!("#[{name}]");
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 c7f241f2f..bd7ce2f1d 100644
--- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs
@@ -2,7 +2,7 @@ use expect_test::{expect, Expect};
use ide_db::base_db::{FileLoader, FileRange};
use syntax::TextRange;
-use crate::{fixture, hover::HoverDocFormat, HoverConfig};
+use crate::{fixture, HoverConfig, HoverDocFormat};
fn check_hover_no_result(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
@@ -10,8 +10,9 @@ fn check_hover_no_result(ra_fixture: &str) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -26,8 +27,9 @@ fn check(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -47,8 +49,9 @@ fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -68,8 +71,9 @@ fn check_hover_no_markdown(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::PlainText),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::PlainText,
},
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
)
@@ -89,8 +93,9 @@ fn check_actions(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
FileRange { file_id, range: position.range_or_empty() },
)
@@ -105,8 +110,9 @@ fn check_hover_range(ra_fixture: &str, expect: Expect) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
range,
)
@@ -121,8 +127,9 @@ fn check_hover_range_no_results(ra_fixture: &str) {
.hover(
&HoverConfig {
links_in_hover: false,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: HoverDocFormat::Markdown,
},
range,
)
@@ -207,37 +214,20 @@ m!(ab$0c);
}
#[test]
-fn hover_shows_type_of_an_expression() {
- check(
- r#"
-pub fn foo() -> u32 { 1 }
-
-fn main() {
- let foo_test = foo()$0;
-}
-"#,
- expect![[r#"
- *foo()*
- ```rust
- u32
- ```
- "#]],
- );
-}
-
-#[test]
fn hover_remove_markdown_if_configured() {
check_hover_no_markdown(
r#"
pub fn foo() -> u32 { 1 }
fn main() {
- let foo_test = foo()$0;
+ let foo_test = foo$0();
}
"#,
expect![[r#"
- *foo()*
- u32
+ *foo*
+ test
+
+ pub fn foo() -> u32
"#]],
);
}
@@ -297,33 +287,6 @@ fn main() { let foo_test = fo$0o(); }
"#]],
);
- // Multiple candidates but results are ambiguous.
- check(
- r#"
-//- /a.rs
-pub fn foo() -> u32 { 1 }
-
-//- /b.rs
-pub fn foo() -> &str { "" }
-
-//- /c.rs
-pub fn foo(a: u32, b: u32) {}
-
-//- /main.rs
-mod a;
-mod b;
-mod c;
-
-fn main() { let foo_test = fo$0o(); }
- "#,
- expect![[r#"
- *foo*
- ```rust
- {unknown}
- ```
- "#]],
- );
-
// Use literal `crate` in path
check(
r#"
@@ -527,6 +490,7 @@ fn hover_field_offset() {
// Hovering over the field when instantiating
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { fiel$0d_a: u8, field_b: i32, field_c: i16 }
"#,
expect![[r#"
@@ -548,6 +512,7 @@ fn hover_shows_struct_field_info() {
// Hovering over the field when instantiating
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { field_a: u32 }
fn main() {
@@ -570,6 +535,7 @@ fn main() {
// Hovering over the field in the definition
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
struct Foo { field_a$0: u32 }
fn main() {
@@ -1184,33 +1150,19 @@ fn test_hover_through_func_in_macro_recursive() {
macro_rules! id_deep { ($($tt:tt)*) => { $($tt)* } }
macro_rules! id { ($($tt:tt)*) => { id_deep!($($tt)*) } }
fn bar() -> u32 { 0 }
-fn foo() { let a = id!([0u32, bar($0)] ); }
+fn foo() { let a = id!([0u32, bar$0()] ); }
"#,
expect![[r#"
- *bar()*
- ```rust
- u32
- ```
- "#]],
- );
-}
+ *bar*
-#[test]
-fn test_hover_through_literal_string_in_macro() {
- check(
- r#"
-macro_rules! arr { ($($tt:tt)*) => { [$($tt)*] } }
-fn foo() {
- let mastered_for_itunes = "";
- let _ = arr!("Tr$0acks", &mastered_for_itunes);
-}
-"#,
- expect![[r#"
- *"Tracks"*
- ```rust
- &str
- ```
- "#]],
+ ```rust
+ test
+ ```
+
+ ```rust
+ fn bar() -> u32
+ ```
+ "#]],
);
}
@@ -1515,6 +1467,8 @@ fn my() {}
fn test_hover_struct_doc_comment() {
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
+
/// This is an example
/// multiline doc
///
@@ -1573,7 +1527,7 @@ fn foo() { let bar = Ba$0r; }
```
```rust
- struct Bar // size = 0, align = 1
+ struct Bar
```
---
@@ -1602,7 +1556,7 @@ fn foo() { let bar = Ba$0r; }
```
```rust
- struct Bar // size = 0, align = 1
+ struct Bar
```
---
@@ -1630,7 +1584,7 @@ pub struct B$0ar
```
```rust
- pub struct Bar // size = 0, align = 1
+ pub struct Bar
```
---
@@ -1657,7 +1611,7 @@ pub struct B$0ar
```
```rust
- pub struct Bar // size = 0, align = 1
+ pub struct Bar
```
---
@@ -2959,6 +2913,8 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new$0(); }
fn hover_field_pat_shorthand_ref_match_ergonomics() {
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
+
struct S {
f: i32,
}
@@ -4398,6 +4354,7 @@ fn main() {
fn hover_intra_doc_links() {
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
pub mod theitem {
/// This is the item. Cool!
@@ -4539,7 +4496,7 @@ trait A where
fn string_shadowed_with_inner_items() {
check(
r#"
-//- /main.rs crate:main deps:alloc
+//- /main.rs crate:main deps:alloc target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
/// Custom `String` type.
struct String;
@@ -5234,7 +5191,7 @@ foo_macro!(
```
```rust
- pub struct Foo // size = 0, align = 1
+ pub struct Foo
```
---
@@ -5248,6 +5205,8 @@ foo_macro!(
fn hover_intra_in_attr() {
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
+
#[doc = "Doc comment for [`Foo$0`]"]
pub struct Foo(i32);
"#,
@@ -5368,6 +5327,8 @@ enum Enum {
fn hover_record_variant_field() {
check(
r#"
+//- /main.rs target_data_layout:e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128
+
enum Enum {
RecordV { field$0: u32 }
}
@@ -5573,3 +5534,116 @@ fn main() {
"#]],
);
}
+
+#[test]
+fn hover_underscore_pat() {
+ check(
+ r#"
+fn main() {
+ let _$0 = 0;
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ let (_$0,) = (0,);
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_underscore_expr() {
+ check(
+ r#"
+fn main() {
+ _$0 = 0;
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+ check(
+ r#"
+fn main() {
+ (_$0,) = (0,);
+}
+"#,
+ expect![[r#"
+ *_*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+}
+
+#[test]
+fn hover_underscore_type() {
+ check_hover_no_result(
+ r#"
+fn main() {
+ let x: _$0 = 0;
+}
+"#,
+ );
+ check_hover_no_result(
+ r#"
+fn main() {
+ let x: (_$0,) = (0,);
+}
+"#,
+ );
+}
+
+#[test]
+fn hover_call_parens() {
+ check(
+ r#"
+fn foo() -> i32 {}
+fn main() {
+ foo($0);
+}
+"#,
+ expect![[r#"
+ *)*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+ check(
+ r#"
+struct S;
+impl S {
+ fn foo(self) -> i32 {}
+}
+fn main() {
+ S.foo($0);
+}
+"#,
+ expect![[r#"
+ *)*
+ ```rust
+ i32
+ ```
+ "#]],
+ );
+}
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 48a7bbfec..ac477339e 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints.rs
@@ -4,13 +4,16 @@ use std::{
};
use either::Either;
-use hir::{known, HasVisibility, HirDisplay, HirWrite, ModuleDef, ModuleDefId, Semantics};
+use hir::{
+ known, HasVisibility, HirDisplay, HirDisplayError, HirWrite, ModuleDef, ModuleDefId, Semantics,
+};
use ide_db::{base_db::FileRange, famous_defs::FamousDefs, RootDatabase};
use itertools::Itertools;
+use smallvec::{smallvec, SmallVec};
use stdx::never;
use syntax::{
ast::{self, AstNode},
- match_ast, NodeOrToken, SyntaxNode, TextRange, TextSize,
+ match_ast, NodeOrToken, SyntaxNode, TextRange,
};
use crate::{navigation_target::TryToNav, FileId};
@@ -28,7 +31,6 @@ 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,
@@ -83,75 +85,108 @@ pub enum AdjustmentHintsMode {
PreferPostfix,
}
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InlayKind {
- BindingModeHint,
- ChainingHint,
- ClosingBraceHint,
- ClosureReturnTypeHint,
- GenericParamListHint,
- AdjustmentHint,
- AdjustmentHintPostfix,
- LifetimeHint,
- ParameterHint,
- TypeHint,
- DiscriminantHint,
+ BindingMode,
+ Chaining,
+ ClosingBrace,
+ ClosureReturnType,
+ GenericParamList,
+ Adjustment,
+ AdjustmentPostfix,
+ Lifetime,
+ Parameter,
+ Type,
+ Discriminant,
OpeningParenthesis,
ClosingParenthesis,
}
#[derive(Debug)]
pub struct InlayHint {
+ /// The text range this inlay hint applies to.
pub range: TextRange,
+ /// The kind of this inlay hint. This is used to determine side and padding of the hint for
+ /// rendering purposes.
pub kind: InlayKind,
+ /// The actual label to show in the inlay hint.
pub label: InlayHintLabel,
- pub tooltip: Option<InlayTooltip>,
+}
+
+impl InlayHint {
+ fn closing_paren(range: TextRange) -> InlayHint {
+ InlayHint { range, kind: InlayKind::ClosingParenthesis, label: InlayHintLabel::from(")") }
+ }
+ fn opening_paren(range: TextRange) -> InlayHint {
+ InlayHint { range, kind: InlayKind::OpeningParenthesis, label: InlayHintLabel::from("(") }
+ }
}
#[derive(Debug)]
pub enum InlayTooltip {
String(String),
- HoverRanged(FileId, TextRange),
- HoverOffset(FileId, TextSize),
+ Markdown(String),
}
#[derive(Default)]
pub struct InlayHintLabel {
- pub parts: Vec<InlayHintLabelPart>,
+ pub parts: SmallVec<[InlayHintLabelPart; 1]>,
}
impl InlayHintLabel {
- pub fn as_simple_str(&self) -> Option<&str> {
- match &*self.parts {
- [part] => part.as_simple_str(),
- _ => None,
+ pub fn simple(
+ s: impl Into<String>,
+ tooltip: Option<InlayTooltip>,
+ linked_location: Option<FileRange>,
+ ) -> InlayHintLabel {
+ InlayHintLabel {
+ parts: smallvec![InlayHintLabelPart { text: s.into(), linked_location, tooltip }],
}
}
pub fn prepend_str(&mut self, s: &str) {
match &mut *self.parts {
- [part, ..] if part.as_simple_str().is_some() => part.text = format!("{s}{}", part.text),
- _ => self.parts.insert(0, InlayHintLabelPart { text: s.into(), linked_location: None }),
+ [InlayHintLabelPart { text, linked_location: None, tooltip: None }, ..] => {
+ text.insert_str(0, s)
+ }
+ _ => self.parts.insert(
+ 0,
+ InlayHintLabelPart { text: s.into(), linked_location: None, tooltip: None },
+ ),
}
}
pub fn append_str(&mut self, s: &str) {
match &mut *self.parts {
- [.., part] if part.as_simple_str().is_some() => part.text.push_str(s),
- _ => self.parts.push(InlayHintLabelPart { text: s.into(), linked_location: None }),
+ [.., InlayHintLabelPart { text, linked_location: None, tooltip: None }] => {
+ text.push_str(s)
+ }
+ _ => self.parts.push(InlayHintLabelPart {
+ text: s.into(),
+ linked_location: None,
+ tooltip: None,
+ }),
}
}
}
impl From<String> for InlayHintLabel {
fn from(s: String) -> Self {
- Self { parts: vec![InlayHintLabelPart { text: s, linked_location: None }] }
+ Self {
+ parts: smallvec![InlayHintLabelPart { text: s, linked_location: None, tooltip: None }],
+ }
}
}
impl From<&str> for InlayHintLabel {
fn from(s: &str) -> Self {
- Self { parts: vec![InlayHintLabelPart { text: s.into(), linked_location: None }] }
+ Self {
+ parts: smallvec![InlayHintLabelPart {
+ text: s.into(),
+ linked_location: None,
+ tooltip: None
+ }],
+ }
}
}
@@ -175,25 +210,25 @@ pub struct InlayHintLabelPart {
/// When setting this, no tooltip must be set on the containing hint, or VS Code will display
/// them both.
pub linked_location: Option<FileRange>,
-}
-
-impl InlayHintLabelPart {
- pub fn as_simple_str(&self) -> Option<&str> {
- match self {
- Self { text, linked_location: None } => Some(text),
- _ => None,
- }
- }
+ /// The tooltip to show when hovering over the inlay hint, this may invoke other actions like
+ /// hover requests to show.
+ pub tooltip: Option<InlayTooltip>,
}
impl fmt::Debug for InlayHintLabelPart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match self.as_simple_str() {
- Some(string) => string.fmt(f),
- None => f
+ match self {
+ Self { text, linked_location: None, tooltip: None } => text.fmt(f),
+ Self { text, linked_location, tooltip } => f
.debug_struct("InlayHintLabelPart")
- .field("text", &self.text)
- .field("linked_location", &self.linked_location)
+ .field("text", text)
+ .field("linked_location", linked_location)
+ .field(
+ "tooltip",
+ &tooltip.as_ref().map_or("", |it| match it {
+ InlayTooltip::String(it) | InlayTooltip::Markdown(it) => it,
+ }),
+ )
.finish(),
}
}
@@ -204,7 +239,6 @@ struct InlayHintLabelBuilder<'a> {
db: &'a RootDatabase,
result: InlayHintLabel,
last_part: String,
- location_link_enabled: bool,
location: Option<FileRange>,
}
@@ -216,9 +250,6 @@ impl fmt::Write for InlayHintLabelBuilder<'_> {
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");
}
@@ -230,9 +261,6 @@ impl HirWrite for InlayHintLabelBuilder<'_> {
}
fn end_location_link(&mut self) {
- if !self.location_link_enabled {
- return;
- }
self.make_new_part();
}
}
@@ -242,6 +270,7 @@ impl InlayHintLabelBuilder<'_> {
self.result.parts.push(InlayHintLabelPart {
text: take(&mut self.last_part),
linked_location: self.location.take(),
+ tooltip: None,
});
}
@@ -262,34 +291,51 @@ fn label_of_ty(
mut max_length: Option<usize>,
ty: hir::Type,
label_builder: &mut InlayHintLabelBuilder<'_>,
- ) {
+ ) -> Result<(), HirDisplayError> {
let iter_item_type = hint_iterator(sema, famous_defs, &ty);
match iter_item_type {
- Some(ty) => {
- const LABEL_START: &str = "impl Iterator<Item = ";
+ Some((iter_trait, item, ty)) => {
+ const LABEL_START: &str = "impl ";
+ const LABEL_ITERATOR: &str = "Iterator";
+ const LABEL_MIDDLE: &str = "<";
+ const LABEL_ITEM: &str = "Item";
+ const LABEL_MIDDLE2: &str = " = ";
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);
+ max_length = max_length.map(|len| {
+ len.saturating_sub(
+ LABEL_START.len()
+ + LABEL_ITERATOR.len()
+ + LABEL_MIDDLE.len()
+ + LABEL_MIDDLE2.len()
+ + LABEL_END.len(),
+ )
+ });
+
+ label_builder.write_str(LABEL_START)?;
+ label_builder.start_location_link(ModuleDef::from(iter_trait).into());
+ label_builder.write_str(LABEL_ITERATOR)?;
+ label_builder.end_location_link();
+ label_builder.write_str(LABEL_MIDDLE)?;
+ label_builder.start_location_link(ModuleDef::from(item).into());
+ label_builder.write_str(LABEL_ITEM)?;
+ label_builder.end_location_link();
+ label_builder.write_str(LABEL_MIDDLE2)?;
+ rec(sema, famous_defs, max_length, ty, label_builder)?;
+ label_builder.write_str(LABEL_END)?;
+ Ok(())
}
- };
+ None => 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 _ = rec(sema, famous_defs, config.max_length, ty, &mut label_builder);
let r = label_builder.finish();
Some(r)
}
@@ -383,11 +429,9 @@ fn hints(
// 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::Enum(it) => discriminant::enum_hints(hints, famous_defs, config, file_id, 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,
@@ -395,12 +439,12 @@ fn hints(
};
}
-/// Checks if the type is an Iterator from std::iter and returns its item type.
+/// Checks if the type is an Iterator from std::iter and returns the iterator trait and the item type of the concrete iterator.
fn hint_iterator(
sema: &Semantics<'_, RootDatabase>,
famous_defs: &FamousDefs<'_, '_>,
ty: &hir::Type,
-) -> Option<hir::Type> {
+) -> Option<(hir::Trait, hir::TypeAlias, hir::Type)> {
let db = sema.db;
let strukt = ty.strip_references().as_adt()?;
let krate = strukt.module(db).krate();
@@ -423,7 +467,7 @@ fn hint_iterator(
_ => None,
})?;
if let Some(ty) = ty.normalize_trait_assoc_type(db, &[], assoc_type_item) {
- return Some(ty);
+ return Some((iter_trait, assoc_type_item, ty));
}
}
@@ -447,7 +491,6 @@ mod tests {
use super::ClosureReturnTypeHints;
pub(super) const DISABLED_CONFIG: InlayHintsConfig = InlayHintsConfig {
- location_links: false,
discriminant_hints: DiscriminantHints::Never,
render_colons: false,
type_hints: false,
@@ -465,8 +508,6 @@ mod tests {
max_length: None,
closing_brace_hints_min_lines: None,
};
- 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,
@@ -474,7 +515,7 @@ mod tests {
closure_return_type_hints: ClosureReturnTypeHints::WithBlock,
binding_mode_hints: true,
lifetime_elision_hints: LifetimeElisionHints::Always,
- ..DISABLED_CONFIG_WITH_LINKS
+ ..DISABLED_CONFIG
};
#[track_caller]
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
index bdd7c05e0..188eb7f97 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs
@@ -3,15 +3,19 @@
//! let _: u32 = /* <never-to-any> */ loop {};
//! let _: &u32 = /* &* */ &mut 0;
//! ```
-use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics};
+use hir::{Adjust, Adjustment, AutoBorrow, HirDisplay, Mutability, PointerCast, Safety, Semantics};
use ide_db::RootDatabase;
+use stdx::never;
use syntax::{
ast::{self, make, AstNode},
ted,
};
-use crate::{AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintsConfig, InlayKind};
+use crate::{
+ AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
+ InlayTooltip,
+};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -44,27 +48,12 @@ pub(super) fn hints(
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,
- });
+ acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
}
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,
- });
+ acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
+ acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
}
let (mut tmp0, mut tmp1);
@@ -76,72 +65,71 @@ pub(super) fn hints(
&mut tmp1
};
- for adjustment in iter {
- if adjustment.source == adjustment.target {
+ for Adjustment { source, target, kind } in iter {
+ if source == target {
continue;
}
// FIXME: Add some nicer tooltips to each of these
- let text = match adjustment.kind {
+ let (text, coercion) = match kind {
Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
- "<never-to-any>"
+ ("<never-to-any>", "never to any")
+ }
+ Adjust::Deref(_) => ("*", "dereference"),
+ Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => ("&", "borrow"),
+ Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => ("&mut ", "unique borrow"),
+ Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => {
+ ("&raw const ", "const pointer borrow")
+ }
+ Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => {
+ ("&raw mut ", "mut pointer borrow")
}
- 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::ReifyFnPointer => {
+ ("<fn-item-to-fn-pointer>", "fn item to fn pointer")
+ }
+ PointerCast::UnsafeFnPointer => (
+ "<safe-fn-pointer-to-unsafe-fn-pointer>",
+ "safe fn pointer to unsafe fn pointer",
+ ),
PointerCast::ClosureFnPointer(Safety::Unsafe) => {
- "<closure-to-unsafe-fn-pointer>"
+ ("<closure-to-unsafe-fn-pointer>", "closure to unsafe fn pointer")
+ }
+ PointerCast::ClosureFnPointer(Safety::Safe) => {
+ ("<closure-to-fn-pointer>", "closure to fn pointer")
+ }
+ PointerCast::MutToConstPointer => {
+ ("<mut-ptr-to-const-ptr>", "mut ptr to const ptr")
}
- 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>",
+ PointerCast::ArrayToPointer => ("<array-ptr-to-element-ptr>", ""),
+ PointerCast::Unsize => ("<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,
+ kind: if postfix { InlayKind::AdjustmentPostfix } else { InlayKind::Adjustment },
+ label: InlayHintLabel::simple(
+ if postfix { format!(".{}", text.trim_end()) } else { text.to_owned() },
+ Some(InlayTooltip::Markdown(format!(
+ "`{}` → `{}` ({coercion} coercion)",
+ source.display(sema.db),
+ target.display(sema.db),
+ ))),
+ 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,
- });
+ acc.push(InlayHint::opening_paren(expr.syntax().text_range()));
+ acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
}
if needs_outer_parens {
- acc.push(InlayHint {
- range: expr.syntax().text_range(),
- kind: InlayKind::ClosingParenthesis,
- label: ")".into(),
- tooltip: None,
- });
+ acc.push(InlayHint::closing_paren(expr.syntax().text_range()));
}
Some(())
}
@@ -223,16 +211,21 @@ fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool,
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!() };
+ let Some(expr) = (|| {
+ if postfix {
+ let ast::Expr::TryExpr(e) = &dummy_expr else { return None };
+ let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
- 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()
+ } else {
+ let ast::Expr::RefExpr(e) = &dummy_expr else { return None };
+ let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
- e.expr().unwrap()
+ e.expr()
+ }
+ })() else {
+ never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr);
+ return (true, true)
};
// At this point
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
index adec19c76..4af7f9bdb 100644
--- 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
@@ -12,9 +12,7 @@ use syntax::{
match_ast,
};
-use crate::{
- inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
-};
+use crate::{inlay_hints::closure_has_block_body, InlayHint, InlayHintsConfig, InlayKind};
use super::label_of_ty;
@@ -22,7 +20,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- file_id: FileId,
+ _file_id: FileId,
pat: &ast::IdentPat,
) -> Option<()> {
if !config.type_hints {
@@ -50,12 +48,8 @@ pub(super) fn hints(
Some(name) => name.syntax().text_range(),
None => pat.syntax().text_range(),
},
- kind: InlayKind::TypeHint,
+ kind: InlayKind::Type,
label,
- tooltip: pat
- .name()
- .map(|it| it.syntax().text_range())
- .map(|it| InlayTooltip::HoverRanged(file_id, it)),
});
Some(())
@@ -73,28 +67,23 @@ fn should_not_display_type_hint(
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;
- }
- }
- }
- }
+ if sema.resolve_bind_pat_to_const(bind_pat).is_some() {
+ return true;
}
for node in bind_pat.syntax().ancestors() {
match_ast! {
match node {
- ast::LetStmt(it) => return it.ty().is_some(),
+ ast::LetStmt(it) => {
+ if config.hide_closure_initialization_hints {
+ if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
+ if closure_has_block_body(&closure) {
+ return true;
+ }
+ }
+ }
+ 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),
@@ -194,8 +183,7 @@ mod tests {
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,
+ check, check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG,
};
use crate::ClosureReturnTypeHints;
@@ -291,7 +279,7 @@ fn main() {
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 },
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
r#"
//- minicore: iterators
struct S<T>(T);
@@ -322,22 +310,66 @@ fn main(a: SliceIter<'_, Container>) {
[
InlayHint {
range: 484..554,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "impl Iterator<Item = impl Iterator<Item = &&str>>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "impl ",
+ InlayHintLabelPart {
+ text: "Iterator",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2611..2619,
+ },
+ ),
+ tooltip: "",
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Item",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2643..2647,
+ },
+ ),
+ tooltip: "",
+ },
+ " = impl ",
+ InlayHintLabelPart {
+ text: "Iterator",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2611..2619,
+ },
+ ),
+ tooltip: "",
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Item",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2643..2647,
+ },
),
- 484..554,
- ),
- ),
+ tooltip: "",
+ },
+ " = &&str>>",
+ ],
},
InlayHint {
range: 484..485,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -350,6 +382,7 @@ fn main(a: SliceIter<'_, Container>) {
range: 289..298,
},
),
+ tooltip: "",
},
"<",
InlayHintLabelPart {
@@ -362,17 +395,10 @@ fn main(a: SliceIter<'_, Container>) {
range: 238..247,
},
),
+ tooltip: "",
},
">",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 484..485,
- ),
- ),
},
]
"#]],
@@ -537,6 +563,21 @@ fn main() {
}
#[test]
+ fn const_pats_have_no_type_hints() {
+ check_types(
+ r#"
+const FOO: usize = 0;
+
+fn main() {
+ match 0 {
+ FOO => (),
+ _ => ()
+ }
+}"#,
+ );
+ }
+
+ #[test]
fn let_statement() {
check_types(
r#"
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
index a0166d004..5d9729263 100644
--- 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
@@ -7,7 +7,7 @@ use ide_db::RootDatabase;
use syntax::ast::{self, AstNode};
-use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+use crate::{InlayHint, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -29,8 +29,17 @@ pub(super) fn hints(
_ => None,
})
.last();
- let range =
- outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range();
+ let range = outer_paren_pat.as_ref().map_or_else(
+ || match pat {
+ // for ident patterns that @ bind a name, render the un-ref patterns in front of the inner pattern
+ // instead of the name as that makes it more clear and doesn't really change the outcome
+ ast::Pat::IdentPat(it) => {
+ it.pat().map_or_else(|| it.syntax().text_range(), |it| it.syntax().text_range())
+ }
+ it => it.syntax().text_range(),
+ },
+ |it| it.syntax().text_range(),
+ );
let pattern_adjustments = sema.pattern_adjustments(pat);
pattern_adjustments.iter().for_each(|ty| {
let reference = ty.is_reference();
@@ -40,12 +49,7 @@ pub(super) fn hints(
(true, false) => "&",
_ => return,
};
- acc.push(InlayHint {
- range,
- kind: InlayKind::BindingModeHint,
- label: r.to_string().into(),
- tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
- });
+ acc.push(InlayHint { range, kind: InlayKind::BindingMode, label: r.to_string().into() });
});
match pat {
ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
@@ -57,24 +61,13 @@ pub(super) fn hints(
};
acc.push(InlayHint {
range: pat.syntax().text_range(),
- kind: InlayKind::BindingModeHint,
+ kind: InlayKind::BindingMode,
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,
- });
+ acc.push(InlayHint::opening_paren(pat.syntax().text_range()));
+ acc.push(InlayHint::closing_paren(pat.syntax().text_range()));
}
_ => (),
}
@@ -139,4 +132,20 @@ fn __(
}"#,
);
}
+
+ #[test]
+ fn hints_binding_modes_complex_ident_pat() {
+ check_with_config(
+ InlayHintsConfig { binding_mode_hints: true, ..DISABLED_CONFIG },
+ r#"
+struct Struct {
+ field: &'static str,
+}
+fn foo(s @ Struct { field, .. }: &Struct) {}
+ //^^^^^^^^^^^^^^^^^^^^^^^^ref
+ //^^^^^^^^^^^^^^^^^^^^&
+ //^^^^^ref
+"#,
+ );
+ }
}
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
index 8810d5d34..0c54f084c 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/chaining.rs
@@ -5,7 +5,7 @@ use syntax::{
Direction, NodeOrToken, SyntaxKind, T,
};
-use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+use crate::{FileId, InlayHint, InlayHintsConfig, InlayKind};
use super::label_of_ty;
@@ -13,7 +13,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- file_id: FileId,
+ _file_id: FileId,
expr: &ast::Expr,
) -> Option<()> {
if !config.chaining_hints {
@@ -59,9 +59,8 @@ pub(super) fn hints(
}
acc.push(InlayHint {
range: expr.syntax().text_range(),
- kind: InlayKind::ChainingHint,
+ kind: InlayKind::Chaining,
label: label_of_ty(famous_defs, config, ty)?,
- tooltip: Some(InlayTooltip::HoverRanged(file_id, expr.syntax().text_range())),
});
}
}
@@ -73,10 +72,7 @@ mod tests {
use expect_test::expect;
use crate::{
- inlay_hints::tests::{
- check_expect, check_with_config, DISABLED_CONFIG, DISABLED_CONFIG_WITH_LINKS,
- TEST_CONFIG,
- },
+ inlay_hints::tests::{check_expect, check_with_config, DISABLED_CONFIG, TEST_CONFIG},
InlayHintsConfig,
};
@@ -88,11 +84,7 @@ mod tests {
#[test]
fn chaining_hints_ignore_comments() {
check_expect(
- InlayHintsConfig {
- type_hints: false,
- chaining_hints: true,
- ..DISABLED_CONFIG_WITH_LINKS
- },
+ InlayHintsConfig { type_hints: false, chaining_hints: true, ..DISABLED_CONFIG },
r#"
struct A(B);
impl A { fn into_b(self) -> B { self.0 } }
@@ -111,7 +103,7 @@ fn main() {
[
InlayHint {
range: 147..172,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -124,21 +116,14 @@ fn main() {
range: 63..64,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 147..172,
- ),
- ),
},
InlayHint {
range: 147..154,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -151,17 +136,10 @@ fn main() {
range: 7..8,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 147..154,
- ),
- ),
},
]
"#]],
@@ -210,33 +188,43 @@ fn main() {
[
InlayHint {
range: 143..190,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "C",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "",
+ InlayHintLabelPart {
+ text: "C",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 51..52,
+ },
),
- 143..190,
- ),
- ),
+ tooltip: "",
+ },
+ "",
+ ],
},
InlayHint {
range: 143..179,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "B",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "",
+ InlayHintLabelPart {
+ text: "B",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 29..30,
+ },
),
- 143..179,
- ),
- ),
+ tooltip: "",
+ },
+ "",
+ ],
},
]
"#]],
@@ -246,7 +234,7 @@ fn main() {
#[test]
fn struct_access_chaining_hints() {
check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
r#"
struct A { pub b: B }
struct B { pub c: C }
@@ -269,7 +257,7 @@ fn main() {
[
InlayHint {
range: 143..190,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -282,21 +270,14 @@ fn main() {
range: 51..52,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 143..190,
- ),
- ),
},
InlayHint {
range: 143..179,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -309,17 +290,10 @@ fn main() {
range: 29..30,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 143..179,
- ),
- ),
},
]
"#]],
@@ -329,7 +303,7 @@ fn main() {
#[test]
fn generic_chaining_hints() {
check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
r#"
struct A<T>(T);
struct B<T>(T);
@@ -353,7 +327,7 @@ fn main() {
[
InlayHint {
range: 246..283,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -366,6 +340,7 @@ fn main() {
range: 23..24,
},
),
+ tooltip: "",
},
"<",
InlayHintLabelPart {
@@ -378,21 +353,14 @@ fn main() {
range: 55..56,
},
),
+ tooltip: "",
},
"<i32, bool>>",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 246..283,
- ),
- ),
},
InlayHint {
range: 246..265,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -405,6 +373,7 @@ fn main() {
range: 7..8,
},
),
+ tooltip: "",
},
"<",
InlayHintLabelPart {
@@ -417,17 +386,10 @@ fn main() {
range: 55..56,
},
),
+ tooltip: "",
},
"<i32, bool>>",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 246..265,
- ),
- ),
},
]
"#]],
@@ -437,7 +399,7 @@ fn main() {
#[test]
fn shorten_iterator_chaining_hints() {
check_expect(
- InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG_WITH_LINKS },
+ InlayHintsConfig { chaining_hints: true, ..DISABLED_CONFIG },
r#"
//- minicore: iterators
use core::iter;
@@ -463,52 +425,106 @@ fn main() {
[
InlayHint {
range: 174..241,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "impl ",
+ InlayHintLabelPart {
+ text: "Iterator",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2611..2619,
+ },
+ ),
+ tooltip: "",
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Item",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2643..2647,
+ },
),
- 174..241,
- ),
- ),
+ tooltip: "",
+ },
+ " = ()>",
+ ],
},
InlayHint {
range: 174..224,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "impl ",
+ InlayHintLabelPart {
+ text: "Iterator",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2611..2619,
+ },
),
- 174..224,
- ),
- ),
+ tooltip: "",
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Item",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2643..2647,
+ },
+ ),
+ tooltip: "",
+ },
+ " = ()>",
+ ],
},
InlayHint {
range: 174..206,
- kind: ChainingHint,
+ kind: Chaining,
label: [
- "impl Iterator<Item = ()>",
- ],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
+ "impl ",
+ InlayHintLabelPart {
+ text: "Iterator",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2611..2619,
+ },
),
- 174..206,
- ),
- ),
+ tooltip: "",
+ },
+ "<",
+ InlayHintLabelPart {
+ text: "Item",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 1,
+ ),
+ range: 2643..2647,
+ },
+ ),
+ tooltip: "",
+ },
+ " = ()>",
+ ],
},
InlayHint {
range: 174..189,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"&mut ",
InlayHintLabelPart {
@@ -521,17 +537,10 @@ fn main() {
range: 24..30,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 174..189,
- ),
- ),
},
]
"#]],
@@ -564,7 +573,7 @@ fn main() {
[
InlayHint {
range: 124..130,
- kind: TypeHint,
+ kind: Type,
label: [
"",
InlayHintLabelPart {
@@ -577,21 +586,14 @@ fn main() {
range: 7..13,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 124..130,
- ),
- ),
},
InlayHint {
range: 145..185,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -604,21 +606,14 @@ fn main() {
range: 7..13,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 145..185,
- ),
- ),
},
InlayHint {
range: 145..168,
- kind: ChainingHint,
+ kind: Chaining,
label: [
"",
InlayHintLabelPart {
@@ -631,32 +626,28 @@ fn main() {
range: 7..13,
},
),
+ tooltip: "",
},
"",
],
- tooltip: Some(
- HoverRanged(
- FileId(
- 0,
- ),
- 145..168,
- ),
- ),
},
InlayHint {
range: 222..228,
- kind: ParameterHint,
+ kind: Parameter,
label: [
- "self",
- ],
- tooltip: Some(
- HoverOffset(
- FileId(
- 0,
+ InlayHintLabelPart {
+ text: "self",
+ linked_location: Some(
+ FileRange {
+ file_id: FileId(
+ 0,
+ ),
+ range: 42..46,
+ },
),
- 42,
- ),
- ),
+ tooltip: "",
+ },
+ ],
},
]
"#]],
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
index e340c64c5..14c11be54 100644
--- 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
@@ -10,9 +10,7 @@ use syntax::{
match_ast, SyntaxKind, SyntaxNode, T,
};
-use crate::{
- inlay_hints::InlayHintLabelPart, FileId, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind,
-};
+use crate::{FileId, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -109,15 +107,11 @@ pub(super) fn hints(
return None;
}
- let linked_location = config
- .location_links
- .then(|| name_range.map(|range| FileRange { file_id, range }))
- .flatten();
+ 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
+ kind: InlayKind::ClosingBrace,
+ label: InlayHintLabel::simple(label, None, linked_location),
});
None
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
index d9929beaa..f03a18b8e 100644
--- 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
@@ -4,7 +4,7 @@ use syntax::ast::{self, AstNode};
use crate::{
inlay_hints::closure_has_block_body, ClosureReturnTypeHints, InlayHint, InlayHintsConfig,
- InlayKind, InlayTooltip,
+ InlayKind,
};
use super::label_of_ty;
@@ -13,7 +13,7 @@ pub(super) fn hints(
acc: &mut Vec<InlayHint>,
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
- file_id: FileId,
+ _file_id: FileId,
closure: ast::ClosureExpr,
) -> Option<()> {
if config.closure_return_type_hints == ClosureReturnTypeHints::Never {
@@ -41,9 +41,8 @@ pub(super) fn hints(
}
acc.push(InlayHint {
range: param_list.syntax().text_range(),
- kind: InlayKind::ClosureReturnTypeHint,
+ kind: InlayKind::ClosureReturnType,
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
index f32c4bdf2..5dd51ad11 100644
--- a/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/inlay_hints/discriminant.rs
@@ -4,27 +4,43 @@
//! Bar/* = 0*/,
//! }
//! ```
-use ide_db::{base_db::FileId, famous_defs::FamousDefs};
+use hir::Semantics;
+use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
use syntax::ast::{self, AstNode, HasName};
-use crate::{DiscriminantHints, InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
+use crate::{
+ DiscriminantHints, InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip,
+};
-pub(super) fn hints(
+pub(super) fn enum_hints(
acc: &mut Vec<InlayHint>,
FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
_: FileId,
- variant: &ast::Variant,
+ enum_: ast::Enum,
) -> 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,
+ let enabled = match config.discriminant_hints {
+ DiscriminantHints::Always => true,
+ DiscriminantHints::Fieldless => {
+ !sema.to_def(&enum_)?.is_data_carrying(sema.db)
+ || enum_.variant_list()?.variants().any(|v| v.expr().is_some())
+ }
+ DiscriminantHints::Never => false,
};
+ if !enabled {
+ return None;
+ }
+ for variant in enum_.variant_list()?.variants() {
+ variant_hints(acc, sema, &variant);
+ }
+ None
+}
+fn variant_hints(
+ acc: &mut Vec<InlayHint>,
+ sema: &Semantics<'_, RootDatabase>,
+ variant: &ast::Variant,
+) -> Option<()> {
if variant.eq_token().is_some() {
return None;
}
@@ -37,19 +53,22 @@ pub(super) fn hints(
let d = v.eval(sema.db);
acc.push(InlayHint {
- range: match field_list {
+ range: match variant.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(),
- })),
+ kind: InlayKind::Discriminant,
+ label: InlayHintLabel::simple(
+ match &d {
+ Ok(v) => format!("{}", v),
+ Err(_) => "?".into(),
+ },
+ Some(InlayTooltip::String(match &d {
+ Ok(_) => "enum variant discriminant".into(),
+ Err(e) => format!("{e:?}").into(),
+ })),
+ None,
+ ),
});
Some(())
@@ -86,15 +105,30 @@ mod tests {
check_discriminants(
r#"
enum Enum {
- Variant,
- //^^^^^^^0
- Variant1,
- //^^^^^^^^1
- Variant2,
- //^^^^^^^^2
- Variant5 = 5,
- Variant6,
- //^^^^^^^^6
+ Variant,
+//^^^^^^^0
+ Variant1,
+//^^^^^^^^1
+ Variant2,
+//^^^^^^^^2
+ Variant5 = 5,
+ Variant6,
+//^^^^^^^^6
+}
+"#,
+ );
+ check_discriminants_fieldless(
+ r#"
+enum Enum {
+ Variant,
+//^^^^^^^0
+ Variant1,
+//^^^^^^^^1
+ Variant2,
+//^^^^^^^^2
+ Variant5 = 5,
+ Variant6,
+//^^^^^^^^6
}
"#,
);
@@ -128,8 +162,22 @@ enum Enum {
enum Enum {
Variant(),
Variant1,
+ Variant2 {},
+ Variant3,
+ Variant5,
+ Variant6,
+}
+"#,
+ );
+ check_discriminants_fieldless(
+ r#"
+enum Enum {
+ Variant(),
+ //^^^^^^^^^0
+ Variant1,
//^^^^^^^^1
Variant2 {},
+ //^^^^^^^^^^^2
Variant3,
//^^^^^^^^3
Variant5 = 5,
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
index 2aa5e3dc7..b7182085b 100644
--- 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
@@ -10,7 +10,7 @@ use syntax::{
SyntaxToken,
};
-use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints};
+use crate::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -23,9 +23,8 @@ pub(super) fn hints(
let mk_lt_hint = |t: SyntaxToken, label: String| InlayHint {
range: t.text_range(),
- kind: InlayKind::LifetimeHint,
+ kind: InlayKind::Lifetime,
label: label.into(),
- tooltip: Some(InlayTooltip::String("Elided lifetime".into())),
};
let param_list = func.param_list()?;
@@ -183,21 +182,19 @@ pub(super) fn hints(
let is_empty = gpl.generic_params().next().is_none();
acc.push(InlayHint {
range: angle_tok.text_range(),
- kind: InlayKind::LifetimeHint,
+ kind: InlayKind::Lifetime,
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,
+ kind: InlayKind::GenericParamList,
label: format!("<{}>", allocated_lifetimes.iter().format(", "),).into(),
- tooltip: Some(InlayTooltip::String("Elided lifetimes".into())),
}),
}
Some(())
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
index 588a0e3b6..1122ee2e3 100644
--- 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
@@ -8,7 +8,7 @@ use syntax::{
SyntaxKind,
};
-use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints};
+use crate::{InlayHint, InlayHintsConfig, InlayKind, LifetimeElisionHints};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -32,9 +32,8 @@ pub(super) fn hints(
let t = ty.amp_token()?;
acc.push(InlayHint {
range: t.text_range(),
- kind: InlayKind::LifetimeHint,
+ kind: InlayKind::Lifetime,
label: "'static".to_owned().into(),
- tooltip: Some(InlayTooltip::String("Elided static lifetime".into())),
});
}
}
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
index ecee67632..9cdae6324 100644
--- 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
@@ -10,7 +10,7 @@ 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};
+use crate::{InlayHint, InlayHintLabel, InlayHintsConfig, InlayKind};
pub(super) fn hints(
acc: &mut Vec<InlayHint>,
@@ -43,21 +43,20 @@ pub(super) fn hints(
!should_hide_param_name_hint(sema, &callable, param_name, arg)
})
.map(|(param, param_name, _, FileRange { range, .. })| {
- let mut tooltip = None;
+ let mut linked_location = 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());
+ linked_location = 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())),
+ kind: InlayKind::Parameter,
+ label: InlayHintLabel::simple(param_name, None, linked_location),
}
});
diff --git a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs
index edc48e84d..1cfde2362 100644
--- a/src/tools/rust-analyzer/crates/ide/src/join_lines.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/join_lines.rs
@@ -161,10 +161,8 @@ fn remove_newline(
}
}
- if config.join_assignments {
- if join_assignments(edit, &prev, &next).is_some() {
- return;
- }
+ if config.join_assignments && join_assignments(edit, &prev, &next).is_some() {
+ return;
}
if config.unwrap_trivial_blocks {
diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs
index 239456cb2..f2b535bdc 100644
--- a/src/tools/rust-analyzer/crates/ide/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs
@@ -82,7 +82,8 @@ pub use crate::{
hover::{HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult},
inlay_hints::{
AdjustmentHints, AdjustmentHintsMode, ClosureReturnTypeHints, DiscriminantHints, InlayHint,
- InlayHintLabel, InlayHintsConfig, InlayKind, InlayTooltip, LifetimeElisionHints,
+ InlayHintLabel, InlayHintLabelPart, InlayHintsConfig, InlayKind, InlayTooltip,
+ LifetimeElisionHints,
},
join_lines::JoinLinesConfig,
markup::Markup,
@@ -114,7 +115,7 @@ pub use ide_db::{
SourceRoot, SourceRootId,
},
label::Label,
- line_index::{LineCol, LineColUtf16, LineIndex},
+ line_index::{LineCol, LineIndex},
search::{ReferenceCategory, SearchScope},
source_change::{FileSystemEdit, SourceChange},
symbol_index::Query,
@@ -236,7 +237,7 @@ impl Analysis {
Ok(Vec::new()),
false,
CrateOrigin::CratesIo { repo: None, name: None },
- None,
+ Err("Analysis::from_single_file has no target layout".into()),
);
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/markdown_remove.rs b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs
index 3ec5c629e..718868c87 100644
--- a/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/markdown_remove.rs
@@ -11,12 +11,146 @@ pub(crate) fn remove_markdown(markdown: &str) -> String {
for event in parser {
match event {
Event::Text(text) | Event::Code(text) => out.push_str(&text),
- Event::SoftBreak | Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => {
- out.push('\n')
+ Event::SoftBreak => out.push(' '),
+ Event::HardBreak | Event::Rule | Event::End(Tag::CodeBlock(_)) => out.push('\n'),
+ Event::End(Tag::Paragraph) => {
+ out.push('\n');
+ out.push('\n');
}
- _ => {}
+ Event::Start(_)
+ | Event::End(_)
+ | Event::Html(_)
+ | Event::FootnoteReference(_)
+ | Event::TaskListMarker(_) => (),
}
}
+ if let Some(p) = out.rfind(|c| c != '\n') {
+ out.drain(p + 1..);
+ }
+
out
}
+
+#[cfg(test)]
+mod tests {
+ use expect_test::expect;
+
+ use super::*;
+
+ #[test]
+ fn smoke_test() {
+ let res = remove_markdown(
+ r##"
+A function or function pointer.
+
+Functions are the primary way code is executed within Rust. Function blocks, usually just
+called functions, can be defined in a variety of different places and be assigned many
+different attributes and modifiers.
+
+Standalone functions that just sit within a module not attached to anything else are common,
+but most functions will end up being inside [`impl`] blocks, either on another type itself, or
+as a trait impl for that type.
+
+```rust
+fn standalone_function() {
+ // code
+}
+
+pub fn public_thing(argument: bool) -> String {
+ // code
+ # "".to_string()
+}
+
+struct Thing {
+ foo: i32,
+}
+
+impl Thing {
+ pub fn new() -> Self {
+ Self {
+ foo: 42,
+ }
+ }
+}
+```
+
+In addition to presenting fixed types in the form of `fn name(arg: type, ..) -> return_type`,
+functions can also declare a list of type parameters along with trait bounds that they fall
+into.
+
+```rust
+fn generic_function<T: Clone>(x: T) -> (T, T, T) {
+ (x.clone(), x.clone(), x.clone())
+}
+
+fn generic_where<T>(x: T) -> T
+ where T: std::ops::Add<Output = T> + Copy
+{
+ x + x + x
+}
+```
+
+Declaring trait bounds in the angle brackets is functionally identical to using a `where`
+clause. It's up to the programmer to decide which works better in each situation, but `where`
+tends to be better when things get longer than one line.
+
+Along with being made public via `pub`, `fn` can also have an [`extern`] added for use in
+FFI.
+
+For more information on the various types of functions and how they're used, consult the [Rust
+book] or the [Reference].
+
+[`impl`]: keyword.impl.html
+[`extern`]: keyword.extern.html
+[Rust book]: ../book/ch03-03-how-functions-work.html
+[Reference]: ../reference/items/functions.html
+"##,
+ );
+ expect![[r#"
+ A function or function pointer.
+
+ Functions are the primary way code is executed within Rust. Function blocks, usually just called functions, can be defined in a variety of different places and be assigned many different attributes and modifiers.
+
+ Standalone functions that just sit within a module not attached to anything else are common, but most functions will end up being inside impl blocks, either on another type itself, or as a trait impl for that type.
+
+ fn standalone_function() {
+ // code
+ }
+
+ pub fn public_thing(argument: bool) -> String {
+ // code
+ # "".to_string()
+ }
+
+ struct Thing {
+ foo: i32,
+ }
+
+ impl Thing {
+ pub fn new() -> Self {
+ Self {
+ foo: 42,
+ }
+ }
+ }
+
+ In addition to presenting fixed types in the form of fn name(arg: type, ..) -> return_type, functions can also declare a list of type parameters along with trait bounds that they fall into.
+
+ fn generic_function<T: Clone>(x: T) -> (T, T, T) {
+ (x.clone(), x.clone(), x.clone())
+ }
+
+ fn generic_where<T>(x: T) -> T
+ where T: std::ops::Add<Output = T> + Copy
+ {
+ x + x + x
+ }
+
+ Declaring trait bounds in the angle brackets is functionally identical to using a where clause. It's up to the programmer to decide which works better in each situation, but where tends to be better when things get longer than one line.
+
+ Along with being made public via pub, fn can also have an extern added for use in FFI.
+
+ For more information on the various types of functions and how they're used, consult the Rust book or the Reference."#]].assert_eq(&res);
+ }
+}
diff --git a/src/tools/rust-analyzer/crates/ide/src/references.rs b/src/tools/rust-analyzer/crates/ide/src/references.rs
index 0f758cfa2..cabbc2872 100644
--- a/src/tools/rust-analyzer/crates/ide/src/references.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/references.rs
@@ -1636,4 +1636,399 @@ pub fn deri$0ve(_stream: TokenStream) -> TokenStream {}
"#]],
);
}
+
+ #[test]
+ fn assoc_items_trait_def() {
+ check(
+ r#"
+trait Trait {
+ const CONST$0: usize;
+}
+
+impl Trait for () {
+ const CONST: usize = 0;
+}
+
+impl Trait for ((),) {
+ const CONST: usize = 0;
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::CONST;
+
+ let _ = T::CONST;
+}
+"#,
+ expect![[r#"
+ CONST Const FileId(0) 18..37 24..29
+
+ FileId(0) 71..76
+ FileId(0) 125..130
+ FileId(0) 183..188
+ FileId(0) 206..211
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ type TypeAlias$0;
+}
+
+impl Trait for () {
+ type TypeAlias = ();
+}
+
+impl Trait for ((),) {
+ type TypeAlias = ();
+}
+
+fn f<T: Trait>() {
+ let _: <() as Trait>::TypeAlias;
+
+ let _: T::TypeAlias;
+}
+"#,
+ expect![[r#"
+ TypeAlias TypeAlias FileId(0) 18..33 23..32
+
+ FileId(0) 66..75
+ FileId(0) 117..126
+ FileId(0) 181..190
+ FileId(0) 207..216
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ fn function$0() {}
+}
+
+impl Trait for () {
+ fn function() {}
+}
+
+impl Trait for ((),) {
+ fn function() {}
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::function;
+
+ let _ = T::function;
+}
+"#,
+ expect![[r#"
+ function Function FileId(0) 18..34 21..29
+
+ FileId(0) 65..73
+ FileId(0) 112..120
+ FileId(0) 166..174
+ FileId(0) 192..200
+ "#]],
+ );
+ }
+
+ #[test]
+ fn assoc_items_trait_impl_def() {
+ check(
+ r#"
+trait Trait {
+ const CONST: usize;
+}
+
+impl Trait for () {
+ const CONST$0: usize = 0;
+}
+
+impl Trait for ((),) {
+ const CONST: usize = 0;
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::CONST;
+
+ let _ = T::CONST;
+}
+"#,
+ expect![[r#"
+ CONST Const FileId(0) 65..88 71..76
+
+ FileId(0) 183..188
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ type TypeAlias;
+}
+
+impl Trait for () {
+ type TypeAlias$0 = ();
+}
+
+impl Trait for ((),) {
+ type TypeAlias = ();
+}
+
+fn f<T: Trait>() {
+ let _: <() as Trait>::TypeAlias;
+
+ let _: T::TypeAlias;
+}
+"#,
+ expect![[r#"
+ TypeAlias TypeAlias FileId(0) 61..81 66..75
+
+ FileId(0) 23..32
+ FileId(0) 117..126
+ FileId(0) 181..190
+ FileId(0) 207..216
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ fn function() {}
+}
+
+impl Trait for () {
+ fn function$0() {}
+}
+
+impl Trait for ((),) {
+ fn function() {}
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::function;
+
+ let _ = T::function;
+}
+"#,
+ expect![[r#"
+ function Function FileId(0) 62..78 65..73
+
+ FileId(0) 166..174
+ "#]],
+ );
+ }
+
+ #[test]
+ fn assoc_items_ref() {
+ check(
+ r#"
+trait Trait {
+ const CONST: usize;
+}
+
+impl Trait for () {
+ const CONST: usize = 0;
+}
+
+impl Trait for ((),) {
+ const CONST: usize = 0;
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::CONST$0;
+
+ let _ = T::CONST;
+}
+"#,
+ expect![[r#"
+ CONST Const FileId(0) 65..88 71..76
+
+ FileId(0) 183..188
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ type TypeAlias;
+}
+
+impl Trait for () {
+ type TypeAlias = ();
+}
+
+impl Trait for ((),) {
+ type TypeAlias = ();
+}
+
+fn f<T: Trait>() {
+ let _: <() as Trait>::TypeAlias$0;
+
+ let _: T::TypeAlias;
+}
+"#,
+ expect![[r#"
+ TypeAlias TypeAlias FileId(0) 18..33 23..32
+
+ FileId(0) 66..75
+ FileId(0) 117..126
+ FileId(0) 181..190
+ FileId(0) 207..216
+ "#]],
+ );
+ check(
+ r#"
+trait Trait {
+ fn function() {}
+}
+
+impl Trait for () {
+ fn function() {}
+}
+
+impl Trait for ((),) {
+ fn function() {}
+}
+
+fn f<T: Trait>() {
+ let _ = <()>::function$0;
+
+ let _ = T::function;
+}
+"#,
+ expect![[r#"
+ function Function FileId(0) 62..78 65..73
+
+ FileId(0) 166..174
+ "#]],
+ );
+ }
+
+ #[test]
+ fn name_clashes() {
+ check(
+ r#"
+trait Foo {
+ fn method$0(&self) -> u8;
+}
+
+struct Bar {
+ method: u8,
+}
+
+impl Foo for Bar {
+ fn method(&self) -> u8 {
+ self.method
+ }
+}
+fn method() {}
+"#,
+ expect![[r#"
+ method Function FileId(0) 16..39 19..25
+
+ FileId(0) 101..107
+ "#]],
+ );
+ check(
+ r#"
+trait Foo {
+ fn method(&self) -> u8;
+}
+
+struct Bar {
+ method$0: u8,
+}
+
+impl Foo for Bar {
+ fn method(&self) -> u8 {
+ self.method
+ }
+}
+fn method() {}
+"#,
+ expect![[r#"
+ method Field FileId(0) 60..70 60..66
+
+ FileId(0) 136..142 Read
+ "#]],
+ );
+ check(
+ r#"
+trait Foo {
+ fn method(&self) -> u8;
+}
+
+struct Bar {
+ method: u8,
+}
+
+impl Foo for Bar {
+ fn method$0(&self) -> u8 {
+ self.method
+ }
+}
+fn method() {}
+"#,
+ expect![[r#"
+ method Function FileId(0) 98..148 101..107
+
+ (no references)
+ "#]],
+ );
+ check(
+ r#"
+trait Foo {
+ fn method(&self) -> u8;
+}
+
+struct Bar {
+ method: u8,
+}
+
+impl Foo for Bar {
+ fn method(&self) -> u8 {
+ self.method$0
+ }
+}
+fn method() {}
+"#,
+ expect![[r#"
+ method Field FileId(0) 60..70 60..66
+
+ FileId(0) 136..142 Read
+ "#]],
+ );
+ check(
+ r#"
+trait Foo {
+ fn method(&self) -> u8;
+}
+
+struct Bar {
+ method: u8,
+}
+
+impl Foo for Bar {
+ fn method(&self) -> u8 {
+ self.method
+ }
+}
+fn method$0() {}
+"#,
+ expect![[r#"
+ method Function FileId(0) 151..165 154..160
+
+ (no references)
+ "#]],
+ );
+ }
+
+ #[test]
+ fn raw_identifier() {
+ check(
+ r#"
+fn r#fn$0() {}
+fn main() { r#fn(); }
+"#,
+ expect![[r#"
+ r#fn Function FileId(0) 0..12 3..7
+
+ FileId(0) 25..29
+ "#]],
+ );
+ }
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/rename.rs b/src/tools/rust-analyzer/crates/ide/src/rename.rs
index 15bdf14fb..c0237e1ed 100644
--- a/src/tools/rust-analyzer/crates/ide/src/rename.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/rename.rs
@@ -13,7 +13,7 @@ use ide_db::{
};
use itertools::Itertools;
use stdx::{always, never};
-use syntax::{ast, AstNode, SyntaxNode};
+use syntax::{ast, utils::is_raw_identifier, AstNode, SmolStr, SyntaxNode, TextRange, TextSize};
use text_edit::TextEdit;
@@ -48,7 +48,13 @@ pub(crate) fn prepare_rename(
frange.range.contains_inclusive(position.offset)
&& frange.file_id == position.file_id
);
- Ok(frange.range)
+
+ Ok(match name_like {
+ ast::NameLike::Lifetime(_) => {
+ TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())
+ }
+ _ => frange.range,
+ })
})
.reduce(|acc, cur| match (acc, cur) {
// ensure all ranges are the same
@@ -116,7 +122,11 @@ pub(crate) fn will_rename_file(
let sema = Semantics::new(db);
let module = sema.to_module_def(file_id)?;
let def = Definition::Module(module);
- let mut change = def.rename(&sema, new_name_stem).ok()?;
+ let mut change = if is_raw_identifier(new_name_stem) {
+ def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()?
+ } else {
+ def.rename(&sema, new_name_stem).ok()?
+ };
change.file_system_edits.clear();
Some(change)
}
@@ -407,7 +417,7 @@ mod tests {
#[test]
fn test_prepare_rename_namelikes() {
check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);
- check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]);
+ check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);
check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);
}
@@ -521,15 +531,19 @@ impl Foo {
#[test]
fn test_rename_to_invalid_identifier_lifetime2() {
- cov_mark::check!(rename_not_a_lifetime_ident_ref);
check(
- "foo",
+ "_",
r#"fn main<'a>(_: &'a$0 ()) {}"#,
- "error: Invalid name `foo`: not a lifetime identifier",
+ r#"error: Invalid name `_`: not a lifetime identifier"#,
);
}
#[test]
+ fn test_rename_accepts_lifetime_without_apostrophe() {
+ check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);
+ }
+
+ #[test]
fn test_rename_to_underscore_invalid() {
cov_mark::check!(rename_underscore_multiple);
check(
@@ -549,6 +563,15 @@ impl Foo {
}
#[test]
+ fn test_rename_mod_invalid_raw_ident() {
+ check(
+ "r#self",
+ r#"mod foo$0 {}"#,
+ "error: Invalid name: `self` cannot be a raw identifier",
+ );
+ }
+
+ #[test]
fn test_rename_for_local() {
check(
"k",
@@ -1277,6 +1300,146 @@ mod bar$0;
}
#[test]
+ fn test_rename_mod_to_raw_ident() {
+ check_expect(
+ "r#fn",
+ r#"
+//- /lib.rs
+mod foo$0;
+
+fn main() { foo::bar::baz(); }
+
+//- /foo.rs
+pub mod bar;
+
+//- /foo/bar.rs
+pub fn baz() {}
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "r#fn",
+ delete: 4..7,
+ },
+ Indel {
+ insert: "r#fn",
+ delete: 22..25,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "fn.rs",
+ },
+ },
+ MoveDir {
+ src: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo",
+ },
+ src_id: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "fn",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_rename_mod_from_raw_ident() {
+ check_expect(
+ "foo",
+ r#"
+//- /lib.rs
+mod r#fn$0;
+
+fn main() { r#fn::bar::baz(); }
+
+//- /fn.rs
+pub mod bar;
+
+//- /fn/bar.rs
+pub fn baz() {}
+"#,
+ expect![[r#"
+ SourceChange {
+ source_file_edits: {
+ FileId(
+ 0,
+ ): TextEdit {
+ indels: [
+ Indel {
+ insert: "foo",
+ delete: 4..8,
+ },
+ Indel {
+ insert: "foo",
+ delete: 23..27,
+ },
+ ],
+ },
+ },
+ file_system_edits: [
+ MoveFile {
+ src: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo.rs",
+ },
+ },
+ MoveDir {
+ src: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "fn",
+ },
+ src_id: FileId(
+ 1,
+ ),
+ dst: AnchoredPathBuf {
+ anchor: FileId(
+ 1,
+ ),
+ path: "foo",
+ },
+ },
+ ],
+ is_snippet: false,
+ }
+ "#]],
+ );
+ }
+
+ #[test]
fn test_enum_variant_from_module_1() {
cov_mark::check!(rename_non_local);
check(
@@ -1832,6 +1995,31 @@ fn foo<'a>() -> &'a () {
}
#[test]
+ fn test_rename_label_new_name_without_apostrophe() {
+ check(
+ "foo",
+ r#"
+fn main() {
+ 'outer$0: loop {
+ 'inner: loop {
+ break 'outer;
+ }
+ }
+}
+ "#,
+ r#"
+fn main() {
+ 'foo: loop {
+ 'inner: loop {
+ break 'foo;
+ }
+ }
+}
+ "#,
+ );
+ }
+
+ #[test]
fn test_self_to_self() {
cov_mark::check!(rename_self_to_self);
check(
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 ae539a5d3..e606072a8 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
@@ -18,7 +18,9 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
let crate_graph = db.crate_graph();
let mut shuffled_ids = crate_graph.iter().collect::<Vec<_>>();
- shuffle(&mut shuffled_ids);
+
+ let mut rng = oorandom::Rand32::new(stdx::rand::seed());
+ stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize);
let mut new_graph = CrateGraph::default();
@@ -52,21 +54,3 @@ pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) {
db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH);
}
-
-fn shuffle<T>(slice: &mut [T]) {
- let mut rng = oorandom::Rand32::new(seed());
-
- let mut remaining = slice.len() - 1;
- while remaining > 0 {
- let index = rng.rand_range(0..remaining as u32);
- slice.swap(remaining, index as usize);
- remaining -= 1;
- }
-}
-
-fn seed() -> u64 {
- use std::collections::hash_map::RandomState;
- use std::hash::{BuildHasher, Hasher};
-
- RandomState::new().build_hasher().finish()
-}
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 f807ba30f..f70ca55a5 100644
--- a/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/signature_help.rs
@@ -4,8 +4,14 @@
use std::collections::BTreeSet;
use either::Either;
-use hir::{AssocItem, GenericParam, HasAttrs, HirDisplay, Semantics, Trait};
-use ide_db::{active_parameter::callable_for_node, base_db::FilePosition};
+use hir::{
+ AssocItem, GenericParam, HasAttrs, HirDisplay, ModuleDef, PathResolution, Semantics, Trait,
+};
+use ide_db::{
+ active_parameter::{callable_for_node, generic_def_for_node},
+ base_db::FilePosition,
+ FxIndexMap,
+};
use stdx::format_to;
use syntax::{
algo,
@@ -37,14 +43,18 @@ impl SignatureHelp {
}
fn push_call_param(&mut self, param: &str) {
- self.push_param('(', param);
+ self.push_param("(", param);
}
fn push_generic_param(&mut self, param: &str) {
- self.push_param('<', param);
+ self.push_param("<", param);
}
- fn push_param(&mut self, opening_delim: char, param: &str) {
+ fn push_record_field(&mut self, param: &str) {
+ self.push_param("{ ", param);
+ }
+
+ fn push_param(&mut self, opening_delim: &str, param: &str) {
if !self.signature.ends_with(opening_delim) {
self.signature.push_str(", ");
}
@@ -85,6 +95,13 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
}
return signature_help_for_generics(&sema, garg_list, token);
},
+ ast::RecordExpr(record) => {
+ let cursor_outside = record.record_expr_field_list().and_then(|list| list.r_curly_token()).as_ref() == Some(&token);
+ if cursor_outside {
+ continue;
+ }
+ return signature_help_for_record_lit(&sema, record, token);
+ },
_ => (),
}
}
@@ -92,8 +109,10 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
// 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;
+ if !matches!(expr, ast::Expr::RecordExpr(..))
+ && expr.syntax().text().contains_char('\n')
+ {
+ break;
}
}
}
@@ -107,18 +126,16 @@ fn signature_help_for_call(
token: SyntaxToken,
) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef
- let mut node = arg_list.syntax().parent()?;
+ let mut nodes = arg_list.syntax().ancestors().skip(1);
let calling_node = loop {
- if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
- if callable
+ if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) {
+ let inside_callable = callable
.arg_list()
- .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
- {
+ .map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()));
+ if inside_callable {
break callable;
}
}
-
- node = node.parent()?;
};
let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?;
@@ -201,59 +218,11 @@ fn signature_help_for_call(
fn signature_help_for_generics(
sema: &Semantics<'_, RootDatabase>,
- garg_list: ast::GenericArgList,
+ arg_list: ast::GenericArgList,
token: SyntaxToken,
) -> Option<SignatureHelp> {
- let arg_list = garg_list
- .syntax()
- .ancestors()
- .filter_map(ast::GenericArgList::cast)
- .find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
-
- let mut active_parameter = arg_list
- .generic_args()
- .take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
- .count();
-
- let first_arg_is_non_lifetime = arg_list
- .generic_args()
- .next()
- .map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
-
- let mut generics_def = if let Some(path) =
- arg_list.syntax().ancestors().find_map(ast::Path::cast)
- {
- let res = sema.resolve_path(&path)?;
- let generic_def: hir::GenericDef = match res {
- hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
- hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
- | hir::PathResolution::Def(hir::ModuleDef::Const(_))
- | hir::PathResolution::Def(hir::ModuleDef::Macro(_))
- | hir::PathResolution::Def(hir::ModuleDef::Module(_))
- | hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
- hir::PathResolution::BuiltinAttr(_)
- | hir::PathResolution::ToolModule(_)
- | hir::PathResolution::Local(_)
- | hir::PathResolution::TypeParam(_)
- | hir::PathResolution::ConstParam(_)
- | hir::PathResolution::SelfType(_)
- | hir::PathResolution::DeriveHelper(_) => return None,
- };
-
- generic_def
- } else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
- {
- // recv.method::<$0>()
- let method = sema.resolve_method_call(&method_call)?;
- method.into()
- } else {
- return None;
- };
-
+ let (mut generics_def, mut active_parameter, first_arg_is_non_lifetime) =
+ generic_def_for_node(sema, &arg_list, &token)?;
let mut res = SignatureHelp {
doc: None,
signature: String::new(),
@@ -292,9 +261,9 @@ fn signature_help_for_generics(
// eg. `None::<u8>`
// We'll use the signature of the enum, but include the docs of the variant.
res.doc = it.docs(db).map(|it| it.into());
- let it = it.parent_enum(db);
- format_to!(res.signature, "enum {}", it.name(db));
- generics_def = it.into();
+ let enum_ = it.parent_enum(db);
+ format_to!(res.signature, "enum {}", enum_.name(db));
+ generics_def = enum_.into();
}
// These don't have generic args that can be specified
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
@@ -368,6 +337,83 @@ fn add_assoc_type_bindings(
}
}
+fn signature_help_for_record_lit(
+ sema: &Semantics<'_, RootDatabase>,
+ record: ast::RecordExpr,
+ token: SyntaxToken,
+) -> Option<SignatureHelp> {
+ let active_parameter = record
+ .record_expr_field_list()?
+ .syntax()
+ .children_with_tokens()
+ .filter_map(syntax::NodeOrToken::into_token)
+ .filter(|t| t.kind() == syntax::T![,])
+ .take_while(|t| t.text_range().start() <= token.text_range().start())
+ .count();
+
+ let mut res = SignatureHelp {
+ doc: None,
+ signature: String::new(),
+ parameters: vec![],
+ active_parameter: Some(active_parameter),
+ };
+
+ let fields;
+
+ let db = sema.db;
+ let path_res = sema.resolve_path(&record.path()?)?;
+ if let PathResolution::Def(ModuleDef::Variant(variant)) = path_res {
+ fields = variant.fields(db);
+ let en = variant.parent_enum(db);
+
+ res.doc = en.docs(db).map(|it| it.into());
+ format_to!(res.signature, "enum {}::{} {{ ", en.name(db), variant.name(db));
+ } else {
+ let adt = match path_res {
+ PathResolution::SelfType(imp) => imp.self_ty(db).as_adt()?,
+ PathResolution::Def(ModuleDef::Adt(adt)) => adt,
+ _ => return None,
+ };
+
+ match adt {
+ hir::Adt::Struct(it) => {
+ fields = it.fields(db);
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "struct {} {{ ", it.name(db));
+ }
+ hir::Adt::Union(it) => {
+ fields = it.fields(db);
+ res.doc = it.docs(db).map(|it| it.into());
+ format_to!(res.signature, "union {} {{ ", it.name(db));
+ }
+ _ => return None,
+ }
+ }
+
+ let mut fields =
+ fields.into_iter().map(|field| (field.name(db), Some(field))).collect::<FxIndexMap<_, _>>();
+ let mut buf = String::new();
+ for field in record.record_expr_field_list()?.fields() {
+ let Some((field, _, ty)) = sema.resolve_record_field(&field) else { continue };
+ let name = field.name(db);
+ format_to!(buf, "{name}: {}", ty.display_truncated(db, Some(20)));
+ res.push_record_field(&buf);
+ buf.clear();
+
+ if let Some(field) = fields.get_mut(&name) {
+ *field = None;
+ }
+ }
+ for (name, field) in fields {
+ let Some(field) = field else { continue };
+ format_to!(buf, "{name}: {}", field.ty(db).display_truncated(db, Some(20)));
+ res.push_record_field(&buf);
+ buf.clear();
+ }
+ res.signature.push_str(" }");
+ Some(res)
+}
+
#[cfg(test)]
mod tests {
use std::iter;
@@ -1405,4 +1451,121 @@ fn take<C, Error>(
"#]],
);
}
+
+ #[test]
+ fn record_literal() {
+ check(
+ r#"
+struct Strukt<T, U = ()> {
+ t: T,
+ u: U,
+ unit: (),
+}
+fn f() {
+ Strukt {
+ u: 0,
+ $0
+ }
+}
+"#,
+ expect![[r#"
+ struct Strukt { u: i32, t: T, unit: () }
+ ------ ^^^^ --------
+ "#]],
+ );
+ }
+
+ #[test]
+ fn record_literal_nonexistent_field() {
+ check(
+ r#"
+struct Strukt {
+ a: u8,
+}
+fn f() {
+ Strukt {
+ b: 8,
+ $0
+ }
+}
+"#,
+ expect![[r#"
+ struct Strukt { a: u8 }
+ -----
+ "#]],
+ );
+ }
+
+ #[test]
+ fn tuple_variant_record_literal() {
+ check(
+ r#"
+enum Opt {
+ Some(u8),
+}
+fn f() {
+ Opt::Some {$0}
+}
+"#,
+ expect![[r#"
+ enum Opt::Some { 0: u8 }
+ ^^^^^
+ "#]],
+ );
+ check(
+ r#"
+enum Opt {
+ Some(u8),
+}
+fn f() {
+ Opt::Some {0:0,$0}
+}
+"#,
+ expect![[r#"
+ enum Opt::Some { 0: u8 }
+ -----
+ "#]],
+ );
+ }
+
+ #[test]
+ fn record_literal_self() {
+ check(
+ r#"
+struct S { t: u8 }
+impl S {
+ fn new() -> Self {
+ Self { $0 }
+ }
+}
+ "#,
+ expect![[r#"
+ struct S { t: u8 }
+ ^^^^^
+ "#]],
+ );
+ }
+
+ #[test]
+ fn test_enum_in_nested_method_in_lambda() {
+ check(
+ r#"
+enum A {
+ A,
+ B
+}
+
+fn bar(_: A) { }
+
+fn main() {
+ let foo = Foo;
+ std::thread::spawn(move || { bar(A:$0) } );
+}
+"#,
+ expect![[r#"
+ fn bar(_: A)
+ ^^^^
+ "#]],
+ );
+ }
}
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 a6b30ba13..3f7f6885f 100644
--- a/src/tools/rust-analyzer/crates/ide/src/static_index.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/static_index.rs
@@ -16,8 +16,7 @@ use crate::{
inlay_hints::AdjustmentHintsMode,
moniker::{def_to_moniker, MonikerResult},
parent_module::crates_for,
- Analysis, Fold, HoverConfig, HoverDocFormat, HoverResult, InlayHint, InlayHintsConfig,
- TryToNav,
+ Analysis, Fold, HoverConfig, HoverResult, InlayHint, InlayHintsConfig, TryToNav,
};
/// A static representation of fully analyzed source code.
@@ -107,7 +106,6 @@ impl StaticIndex<'_> {
.analysis
.inlay_hints(
&InlayHintsConfig {
- location_links: true,
render_colons: true,
discriminant_hints: crate::DiscriminantHints::Fieldless,
type_hints: true,
@@ -138,8 +136,9 @@ impl StaticIndex<'_> {
});
let hover_config = HoverConfig {
links_in_hover: true,
- documentation: Some(HoverDocFormat::Markdown),
+ documentation: true,
keywords: true,
+ format: crate::HoverDocFormat::Markdown,
};
let tokens = tokens.filter(|token| {
matches!(
diff --git a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
index 50371d620..454a250f3 100644
--- a/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/syntax_highlighting.rs
@@ -413,11 +413,10 @@ fn traverse(
let string = ast::String::cast(token);
let string_to_highlight = ast::String::cast(descended_token.clone());
if let Some((string, expanded_string)) = string.zip(string_to_highlight) {
- if string.is_raw() {
- if inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some()
- {
- continue;
- }
+ if string.is_raw()
+ && inject::ra_fixture(hl, sema, config, &string, &expanded_string).is_some()
+ {
+ continue;
}
highlight_format_string(hl, &string, &expanded_string, range);
highlight_escape_string(hl, &string, range.start());
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 2f870d769..fc9b5d3ba 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
@@ -1126,5 +1126,5 @@ fn benchmark_syntax_highlighting_parser() {
.filter(|it| it.highlight.tag == HlTag::Symbol(SymbolKind::Function))
.count()
};
- assert_eq!(hash, 1609);
+ assert_eq!(hash, 1608);
}
diff --git a/src/tools/rust-analyzer/crates/ide/src/typing.rs b/src/tools/rust-analyzer/crates/ide/src/typing.rs
index eba5a4856..c7e403f6b 100644
--- a/src/tools/rust-analyzer/crates/ide/src/typing.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/typing.rs
@@ -205,10 +205,8 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
if expr_stmt.semicolon_token().is_some() {
return None;
}
- } else {
- if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
- return None;
- }
+ } else if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
+ return None;
}
let expr = binop.rhs()?;
@@ -255,6 +253,10 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
return None;
}
+ // Good indicator that we will insert into a bad spot, so bail out.
+ if expr.syntax().descendants().any(|it| it.kind() == SyntaxKind::ERROR) {
+ return None;
+ }
let offset = let_stmt.syntax().text_range().end();
Some(TextEdit::insert(offset, ";".to_string()))
}
@@ -409,15 +411,14 @@ mod tests {
#[test]
fn test_semi_after_let() {
- // do_check(r"
- // fn foo() {
- // let foo =$0
- // }
- // ", r"
- // fn foo() {
- // let foo =;
- // }
- // ");
+ type_char_noop(
+ '=',
+ r"
+fn foo() {
+ let foo =$0
+}
+",
+ );
type_char(
'=',
r#"
@@ -431,17 +432,25 @@ fn foo() {
}
"#,
);
- // do_check(r"
- // fn foo() {
- // let foo =$0
- // let bar = 1;
- // }
- // ", r"
- // fn foo() {
- // let foo =;
- // let bar = 1;
- // }
- // ");
+ type_char_noop(
+ '=',
+ r#"
+fn foo() {
+ let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 {
+ // ...
+ }
+}
+"#,
+ );
+ type_char_noop(
+ '=',
+ r"
+fn foo() {
+ let foo =$0
+ let bar = 1;
+}
+",
+ );
}
#[test]