Auto merge of #17985 - riverbl:explicit-enum-discriminant, r=Veykril
Add explicit enum discriminant assist
Add assist for adding explicit discriminants to all variants of an enum.
Closes #17798.
diff --git a/crates/base-db/src/input.rs b/crates/base-db/src/input.rs
index 3616fa9..032e4a8 100644
--- a/crates/base-db/src/input.rs
+++ b/crates/base-db/src/input.rs
@@ -374,37 +374,6 @@
self.arena.alloc(data)
}
- /// Remove the crate from crate graph. If any crates depend on this crate, the dependency would be replaced
- /// with the second input.
- pub fn remove_and_replace(
- &mut self,
- id: CrateId,
- replace_with: CrateId,
- ) -> Result<(), CyclicDependenciesError> {
- for (x, data) in self.arena.iter() {
- if x == id {
- continue;
- }
- for edge in &data.dependencies {
- if edge.crate_id == id {
- self.check_cycle_after_dependency(edge.crate_id, replace_with)?;
- }
- }
- }
- // if everything was ok, start to replace
- for (x, data) in self.arena.iter_mut() {
- if x == id {
- continue;
- }
- for edge in &mut data.dependencies {
- if edge.crate_id == id {
- edge.crate_id = replace_with;
- }
- }
- }
- Ok(())
- }
-
pub fn add_dep(
&mut self,
from: CrateId,
@@ -412,26 +381,17 @@
) -> Result<(), CyclicDependenciesError> {
let _p = tracing::info_span!("add_dep").entered();
- self.check_cycle_after_dependency(from, dep.crate_id)?;
-
- self.arena[from].add_dep(dep);
- Ok(())
- }
-
- /// Check if adding a dep from `from` to `to` creates a cycle. To figure
- /// that out, look for a path in the *opposite* direction, from `to` to
- /// `from`.
- fn check_cycle_after_dependency(
- &self,
- from: CrateId,
- to: CrateId,
- ) -> Result<(), CyclicDependenciesError> {
- if let Some(path) = self.find_path(&mut FxHashSet::default(), to, from) {
+ // Check if adding a dep from `from` to `to` creates a cycle. To figure
+ // that out, look for a path in the *opposite* direction, from `to` to
+ // `from`.
+ if let Some(path) = self.find_path(&mut FxHashSet::default(), dep.crate_id, from) {
let path = path.into_iter().map(|it| (it, self[it].display_name.clone())).collect();
let err = CyclicDependenciesError { path };
- assert!(err.from().0 == from && err.to().0 == to);
+ assert!(err.from().0 == from && err.to().0 == dep.crate_id);
return Err(err);
}
+
+ self.arena[from].add_dep(dep);
Ok(())
}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 96a6e6f..9536f12 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -2207,6 +2207,35 @@
db.function_data(self.id).is_async()
}
+ pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool {
+ if self.is_async(db) {
+ return true;
+ }
+
+ let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false };
+ let Some(future_trait_id) =
+ db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait())
+ else {
+ return false;
+ };
+ let Some(sized_trait_id) =
+ db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait())
+ else {
+ return false;
+ };
+
+ let mut has_impl_future = false;
+ impl_traits
+ .filter(|t| {
+ let fut = t.id == future_trait_id;
+ has_impl_future |= fut;
+ !fut && t.id != sized_trait_id
+ })
+ // all traits but the future trait must be auto traits
+ .all(|t| t.is_auto(db))
+ && has_impl_future
+ }
+
/// Does this function have `#[test]` attribute?
pub fn is_test(self, db: &dyn HirDatabase) -> bool {
db.function_data(self.id).attrs.is_test()
diff --git a/crates/ide-assists/src/handlers/flip_comma.rs b/crates/ide-assists/src/handlers/flip_comma.rs
index f40f271..af2c2c7 100644
--- a/crates/ide-assists/src/handlers/flip_comma.rs
+++ b/crates/ide-assists/src/handlers/flip_comma.rs
@@ -1,4 +1,8 @@
-use syntax::{algo::non_trivia_sibling, Direction, SyntaxKind, T};
+use ide_db::base_db::SourceDatabase;
+use syntax::TextSize;
+use syntax::{
+ algo::non_trivia_sibling, ast, AstNode, Direction, SyntaxKind, SyntaxToken, TextRange, T,
+};
use crate::{AssistContext, AssistId, AssistKind, Assists};
@@ -21,6 +25,8 @@
let comma = ctx.find_token_syntax_at_offset(T![,])?;
let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
+ let (mut prev_text, mut next_text) = (prev.to_string(), next.to_string());
+ let (mut prev_range, mut next_range) = (prev.text_range(), next.text_range());
// Don't apply a "flip" in case of a last comma
// that typically comes before punctuation
@@ -34,17 +40,55 @@
return None;
}
+ if let Some(parent) = comma.parent().and_then(ast::TokenTree::cast) {
+ // An attribute. It often contains a path followed by a token tree (e.g. `align(2)`), so we have
+ // to be smarter.
+ let prev_start =
+ match comma.siblings_with_tokens(Direction::Prev).skip(1).find(|it| it.kind() == T![,])
+ {
+ Some(it) => position_after_token(it.as_token().unwrap()),
+ None => position_after_token(&parent.left_delimiter_token()?),
+ };
+ let prev_end = prev.text_range().end();
+ let next_start = next.text_range().start();
+ let next_end =
+ match comma.siblings_with_tokens(Direction::Next).skip(1).find(|it| it.kind() == T![,])
+ {
+ Some(it) => position_before_token(it.as_token().unwrap()),
+ None => position_before_token(&parent.right_delimiter_token()?),
+ };
+ prev_range = TextRange::new(prev_start, prev_end);
+ next_range = TextRange::new(next_start, next_end);
+ let file_text = ctx.db().file_text(ctx.file_id().file_id());
+ prev_text = file_text[prev_range].to_owned();
+ next_text = file_text[next_range].to_owned();
+ }
+
acc.add(
AssistId("flip_comma", AssistKind::RefactorRewrite),
"Flip comma",
comma.text_range(),
|edit| {
- edit.replace(prev.text_range(), next.to_string());
- edit.replace(next.text_range(), prev.to_string());
+ edit.replace(prev_range, next_text);
+ edit.replace(next_range, prev_text);
},
)
}
+fn position_before_token(token: &SyntaxToken) -> TextSize {
+ match non_trivia_sibling(token.clone().into(), Direction::Prev) {
+ Some(prev_token) => prev_token.text_range().end(),
+ None => token.text_range().start(),
+ }
+}
+
+fn position_after_token(token: &SyntaxToken) -> TextSize {
+ match non_trivia_sibling(token.clone().into(), Direction::Next) {
+ Some(prev_token) => prev_token.text_range().start(),
+ None => token.text_range().end(),
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -89,4 +133,18 @@
// See https://github.com/rust-lang/rust-analyzer/issues/7693
check_assist_not_applicable(flip_comma, r#"bar!(a,$0 b)"#);
}
+
+ #[test]
+ fn flip_comma_attribute() {
+ check_assist(
+ flip_comma,
+ r#"#[repr(align(2),$0 C)] struct Foo;"#,
+ r#"#[repr(C, align(2))] struct Foo;"#,
+ );
+ check_assist(
+ flip_comma,
+ r#"#[foo(bar, baz(1 + 1),$0 qux, other)] struct Foo;"#,
+ r#"#[foo(bar, qux, baz(1 + 1), other)] struct Foo;"#,
+ );
+ }
}
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index fc6e1eb..e93bb8d 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -31,14 +31,14 @@
//! }
//! ```
-use hir::HasAttrs;
+use hir::{HasAttrs, Name};
use ide_db::{
documentation::HasDocs, path_transform::PathTransform,
syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
};
use syntax::{
- ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds},
- format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
+ ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds},
+ format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T,
};
use text_edit::TextEdit;
@@ -178,12 +178,36 @@
func: hir::Function,
impl_def: hir::Impl,
) {
- let fn_name = func.name(ctx.db);
+ let fn_name = &func.name(ctx.db);
+ let sugar: &[_] = if func.is_async(ctx.db) {
+ &[AsyncSugaring::Async, AsyncSugaring::Desugar]
+ } else if func.returns_impl_future(ctx.db) {
+ &[AsyncSugaring::Plain, AsyncSugaring::Resugar]
+ } else {
+ &[AsyncSugaring::Plain]
+ };
+ for &sugaring in sugar {
+ add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring);
+ }
+}
- let is_async = func.is_async(ctx.db);
+fn add_function_impl_(
+ acc: &mut Completions,
+ ctx: &CompletionContext<'_>,
+ replacement_range: TextRange,
+ func: hir::Function,
+ impl_def: hir::Impl,
+ fn_name: &Name,
+ async_sugaring: AsyncSugaring,
+) {
+ let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring {
+ "async "
+ } else {
+ ""
+ };
let label = format_smolstr!(
"{}fn {}({})",
- if is_async { "async " } else { "" },
+ async_,
fn_name.display(ctx.db, ctx.edition),
if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." }
);
@@ -195,22 +219,14 @@
});
let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition);
- item.lookup_by(format!(
- "{}fn {}",
- if is_async { "async " } else { "" },
- fn_name.display(ctx.db, ctx.edition)
- ))
- .set_documentation(func.docs(ctx.db))
- .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ item.lookup_by(format!("{}fn {}", async_, fn_name.display(ctx.db, ctx.edition)))
+ .set_documentation(func.docs(ctx.db))
+ .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
if let Some(source) = ctx.sema.source(func) {
- let assoc_item = ast::AssocItem::Fn(source.value);
- if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) {
- let transformed_fn = match transformed_item {
- ast::AssocItem::Fn(func) => func,
- _ => unreachable!(),
- };
-
+ if let Some(transformed_fn) =
+ get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
+ {
let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro());
match ctx.config.snippet_cap {
Some(cap) => {
@@ -227,6 +243,14 @@
}
}
+#[derive(Copy, Clone)]
+enum AsyncSugaring {
+ Desugar,
+ Resugar,
+ Async,
+ Plain,
+}
+
/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
fn get_transformed_assoc_item(
ctx: &CompletionContext<'_>,
@@ -251,6 +275,82 @@
Some(assoc_item)
}
+/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc.
+fn get_transformed_fn(
+ ctx: &CompletionContext<'_>,
+ fn_: ast::Fn,
+ impl_def: hir::Impl,
+ async_: AsyncSugaring,
+) -> Option<ast::Fn> {
+ let trait_ = impl_def.trait_(ctx.db)?;
+ let source_scope = &ctx.sema.scope(fn_.syntax())?;
+ let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?;
+ let transform = PathTransform::trait_impl(
+ target_scope,
+ source_scope,
+ trait_,
+ ctx.sema.source(impl_def)?.value,
+ );
+
+ let fn_ = fn_.clone_for_update();
+ // FIXME: Paths in nested macros are not handled well. See
+ // `macro_generated_assoc_item2` test.
+ transform.apply(fn_.syntax());
+ fn_.remove_attrs_and_docs();
+ match async_ {
+ AsyncSugaring::Desugar => {
+ match fn_.ret_type() {
+ Some(ret_ty) => {
+ let ty = ret_ty.ty()?;
+ ted::replace(
+ ty.syntax(),
+ make::ty(&format!("impl Future<Output = {ty}>"))
+ .syntax()
+ .clone_for_update(),
+ );
+ }
+ None => ted::append_child(
+ fn_.param_list()?.syntax(),
+ make::ret_type(make::ty("impl Future<Output = ()>"))
+ .syntax()
+ .clone_for_update(),
+ ),
+ }
+ fn_.async_token().unwrap().detach();
+ }
+ AsyncSugaring::Resugar => {
+ let ty = fn_.ret_type()?.ty()?;
+ match &ty {
+ // best effort guessing here
+ ast::Type::ImplTraitType(t) => {
+ let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? {
+ ast::Type::PathType(p) => {
+ let p = p.path()?.segment()?;
+ if p.name_ref()?.text() != "Future" {
+ return None;
+ }
+ match p.generic_arg_list()?.generic_args().next()? {
+ ast::GenericArg::AssocTypeArg(a)
+ if a.name_ref()?.text() == "Output" =>
+ {
+ a.ty()
+ }
+ _ => None,
+ }
+ }
+ _ => None,
+ })?;
+ ted::replace(ty.syntax(), output.syntax());
+ }
+ _ => (),
+ }
+ ted::prepend_child(fn_.syntax(), make::token(T![async]));
+ }
+ AsyncSugaring::Async | AsyncSugaring::Plain => (),
+ }
+ Some(fn_)
+}
+
fn add_type_alias_impl(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
@@ -1404,4 +1504,132 @@
"#,
);
}
+
+ #[test]
+ fn impl_fut() {
+ check_edit(
+ "fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ fn foo(&self) -> impl Future<Output = usize> + Send {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn impl_fut_resugared() {
+ check_edit(
+ "async fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ async fn foo(&self) -> usize {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_desugared() {
+ check_edit(
+ "fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ fn foo(&self) -> impl Future<Output = usize> {
+ $0
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn async_() {
+ check_edit(
+ "async fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ async fn foo(&self) -> usize;
+}
+
+impl DesugaredAsyncTrait for () {
+ async fn foo(&self) -> usize {
+ $0
+}
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index 8aad7bf..532d492 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -313,6 +313,7 @@
ct const CONST1: () =
fn async fn function2()
fn fn function1()
+ fn fn function2()
ma makro!(…) macro_rules! makro
md module
ta type Type1 =
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index be99510..2e49af4 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -15,7 +15,7 @@
use stdx::never;
use syntax::{
ast::{self, AstNode, HasGenericParams},
- format_smolstr, match_ast, NodeOrToken, SmolStr, SyntaxNode, TextRange, TextSize, WalkEvent,
+ format_smolstr, match_ast, SmolStr, SyntaxNode, TextRange, TextSize, WalkEvent,
};
use text_edit::TextEdit;
@@ -95,26 +95,27 @@
let famous_defs = FamousDefs(&sema, scope.krate());
let ctx = &mut InlayHintCtx::default();
- let hints = |node| hints(&mut acc, ctx, &famous_defs, config, file_id, node);
- match range_limit {
- // FIXME: This can miss some hints that require the parent of the range to calculate
- Some(range) => match file.covering_element(range) {
- NodeOrToken::Token(_) => return acc,
- NodeOrToken::Node(n) => n
- .preorder()
- .filter(|event| matches!(event, WalkEvent::Enter(node) if range.intersect(node.text_range()).is_some()))
- .for_each(hints),
- },
- None => file.preorder().for_each(hints),
+ let mut hints = |event| {
+ if let Some(node) = handle_event(ctx, event) {
+ hints(&mut acc, ctx, &famous_defs, config, file_id, node);
+ }
};
-
+ let mut preorder = file.preorder();
+ while let Some(event) = preorder.next() {
+ // FIXME: This can miss some hints that require the parent of the range to calculate
+ if matches!((&event, range_limit), (WalkEvent::Enter(node), Some(range)) if range.intersect(node.text_range()).is_none())
+ {
+ preorder.skip_subtree();
+ continue;
+ }
+ hints(event);
+ }
acc
}
#[derive(Default)]
struct InlayHintCtx {
lifetime_stacks: Vec<Vec<SmolStr>>,
- is_param_list: bool,
}
pub(crate) fn inlay_hints_resolve(
@@ -138,20 +139,52 @@
let mut acc = Vec::new();
let ctx = &mut InlayHintCtx::default();
- let hints = |node| hints(&mut acc, ctx, &famous_defs, config, file_id, node);
-
- let mut res = file.clone();
- let res = loop {
- res = match res.child_or_token_at_range(resolve_range) {
- Some(NodeOrToken::Node(n)) if n.text_range() == resolve_range => break n,
- Some(NodeOrToken::Node(n)) => n,
- _ => break res,
- };
+ let mut hints = |event| {
+ if let Some(node) = handle_event(ctx, event) {
+ hints(&mut acc, ctx, &famous_defs, config, file_id, node);
+ }
};
- res.preorder().for_each(hints);
+
+ let mut preorder = file.preorder();
+ while let Some(event) = preorder.next() {
+ // FIXME: This can miss some hints that require the parent of the range to calculate
+ if matches!(&event, WalkEvent::Enter(node) if resolve_range.intersect(node.text_range()).is_none())
+ {
+ preorder.skip_subtree();
+ continue;
+ }
+ hints(event);
+ }
acc.into_iter().find(|hint| hasher(hint) == hash)
}
+fn handle_event(ctx: &mut InlayHintCtx, node: WalkEvent<SyntaxNode>) -> Option<SyntaxNode> {
+ match node {
+ WalkEvent::Enter(node) => {
+ if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
+ let params = node
+ .generic_param_list()
+ .map(|it| {
+ it.lifetime_params()
+ .filter_map(|it| {
+ it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..]))
+ })
+ .collect()
+ })
+ .unwrap_or_default();
+ ctx.lifetime_stacks.push(params);
+ }
+ Some(node)
+ }
+ WalkEvent::Leave(n) => {
+ if ast::AnyHasGenericParams::can_cast(n.kind()) {
+ ctx.lifetime_stacks.pop();
+ }
+ None
+ }
+ }
+}
+
// FIXME: At some point when our hir infra is fleshed out enough we should flip this and traverse the
// HIR instead of the syntax tree.
fn hints(
@@ -160,35 +193,8 @@
famous_defs @ FamousDefs(sema, _): &FamousDefs<'_, '_>,
config: &InlayHintsConfig,
file_id: EditionedFileId,
- node: WalkEvent<SyntaxNode>,
+ node: SyntaxNode,
) {
- let node = match node {
- WalkEvent::Enter(node) => node,
- WalkEvent::Leave(n) => {
- if ast::AnyHasGenericParams::can_cast(n.kind()) {
- ctx.lifetime_stacks.pop();
- // pop
- }
- if ast::ParamList::can_cast(n.kind()) {
- ctx.is_param_list = false;
- // pop
- }
- return;
- }
- };
-
- if let Some(node) = ast::AnyHasGenericParams::cast(node.clone()) {
- let params = node
- .generic_param_list()
- .map(|it| {
- it.lifetime_params()
- .filter_map(|it| it.lifetime().map(|it| format_smolstr!("{}", &it.text()[1..])))
- .collect()
- })
- .unwrap_or_default();
- ctx.lifetime_stacks.push(params);
- }
-
closing_brace::hints(hints, sema, config, file_id, node.clone());
if let Some(any_has_generic_args) = ast::AnyHasGenericArgs::cast(node.clone()) {
generic_param::hints(hints, sema, config, any_has_generic_args);
@@ -242,10 +248,6 @@
ast::Type::PathType(path) => lifetime::fn_path_hints(hints, ctx, famous_defs, config, file_id, path),
_ => Some(()),
},
- ast::ParamList(_) => {
- ctx.is_param_list = true;
- Some(())
- },
_ => Some(()),
}
};
diff --git a/crates/ide/src/inlay_hints/lifetime.rs b/crates/ide/src/inlay_hints/lifetime.rs
index de46367..653e3a6 100644
--- a/crates/ide/src/inlay_hints/lifetime.rs
+++ b/crates/ide/src/inlay_hints/lifetime.rs
@@ -532,7 +532,7 @@
//^^ for<'1>
//^'1
//^'1
-fn fn_ptr2(a: for<'a> fn(&()) -> &())) {}
+fn fn_ptr2(a: for<'a> fn(&()) -> &()) {}
//^'0, $
//^'0
//^'0
diff --git a/crates/project-model/src/workspace.rs b/crates/project-model/src/workspace.rs
index 7834238..4fc9ef3 100644
--- a/crates/project-model/src/workspace.rs
+++ b/crates/project-model/src/workspace.rs
@@ -1554,6 +1554,6 @@
fn add_dep_inner(graph: &mut CrateGraph, from: CrateId, dep: Dependency) {
if let Err(err) = graph.add_dep(from, dep) {
- tracing::error!("{}", err)
+ tracing::warn!("{}", err)
}
}
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index c884216..714c835 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -429,6 +429,8 @@
completion_callable_snippets: CallableCompletionDef = CallableCompletionDef::FillArguments,
/// Whether to show full function/method signatures in completion docs.
completion_fullFunctionSignatures_enable: bool = false,
+ /// Whether to omit deprecated items from autocompletion. By default they are marked as deprecated but not hidden.
+ completion_hideDeprecated: bool = false,
/// Maximum number of completions to return. If `None`, the limit is infinite.
completion_limit: Option<usize> = None,
/// Whether to show postfix snippets like `dbg`, `if`, `not`, etc.
@@ -1443,6 +1445,10 @@
}
}
+ pub fn completion_hide_deprecated(&self) -> bool {
+ *self.completion_hideDeprecated(None)
+ }
+
pub fn detached_files(&self) -> &Vec<AbsPathBuf> {
// FIXME @alibektas : This is the only config that is confusing. If it's a proper configuration
// why is it not among the others? If it's client only which I doubt it is current state should be alright
diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index 6dff8ab..b29268f 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -228,8 +228,12 @@
line_index: &LineIndex,
version: Option<i32>,
tdpp: lsp_types::TextDocumentPositionParams,
- items: Vec<CompletionItem>,
+ mut items: Vec<CompletionItem>,
) -> Vec<lsp_types::CompletionItem> {
+ if config.completion_hide_deprecated() {
+ items.retain(|item| !item.deprecated);
+ }
+
let max_relevance = items.iter().map(|it| it.relevance.score()).max().unwrap_or_default();
let mut res = Vec::with_capacity(items.len());
for item in items {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index abf1a1f..2eb9c1e 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -1162,7 +1162,7 @@
pub(super) static SOURCE_FILE: LazyLock<Parse<SourceFile>> = LazyLock::new(|| {
SourceFile::parse(
- "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
+ "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT,
)
});
diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs
index 29788d0..8592df1 100644
--- a/crates/syntax/src/ted.rs
+++ b/crates/syntax/src/ted.rs
@@ -147,6 +147,11 @@
insert_raw(position, child);
}
+pub fn prepend_child(node: &(impl Into<SyntaxNode> + Clone), child: impl Element) {
+ let position = Position::first_child_of(node);
+ insert(position, child);
+}
+
fn ws_before(position: &Position, new: &SyntaxElement) -> Option<SyntaxToken> {
let prev = match &position.repr {
PositionRepr::FirstChild(_) => return None,
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index e4a8c64..4fcf580 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -283,6 +283,11 @@
--
Whether to show full function/method signatures in completion docs.
--
+[[rust-analyzer.completion.hideDeprecated]]rust-analyzer.completion.hideDeprecated (default: `false`)::
++
+--
+Whether to omit deprecated items from autocompletion. By default they are marked as deprecated but not hidden.
+--
[[rust-analyzer.completion.limit]]rust-analyzer.completion.limit (default: `null`)::
+
--
diff --git a/editors/code/package.json b/editors/code/package.json
index 98e8bbf..0b02946 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1080,6 +1080,16 @@
{
"title": "completion",
"properties": {
+ "rust-analyzer.completion.hideDeprecated": {
+ "markdownDescription": "Whether to omit deprecated items from autocompletion. By default they are marked as deprecated but not hidden.",
+ "default": false,
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "title": "completion",
+ "properties": {
"rust-analyzer.completion.limit": {
"markdownDescription": "Maximum number of completions to return. If `None`, the limit is infinite.",
"default": null,