Repository: rust-lang/rust-analyzer (rust)

Issue:
No completions offered after async keyword
<!--
Troubleshooting guide: https://rust-analyzer.github.io/manual.html#troubleshooting
Forum for questions: https://users.rust-lang.org/c/ide/14

Before submitting, please make sure that you're not running into one of these known issues:

 1. on-the-fly diagnostics are mostly unimplemented (`cargo check` diagnostics will be shown when saving a file): #3107

Otherwise please try to provide information which will help us to fix the issue faster. Minimal reproducible examples with few dependencies are especially lovely <3.
-->

_This issue is a follow-up of #17452_

**rust-analyzer version**: Using Neovim, so the version released in 2024-06-24.

**rustc version**: (eg. output of `rustc -V`) 1.79.0 (129f3b996 2024-06-10)

**editor or extension**: (eg. VSCode, Vim, Emacs, etc. For VSCode users, specify your extension version; for users of other editors, provide the distribution if applicable) Neovim, but the issue also repros in VS Code.

**Reproduction steps**:
1. In a Rust file type `async f`.
2. Request completions.
3. Notice how there are no completions from rust-analyzer, while the expectation is for items like `fn` to be provided.



<!-- TRIAGEBOT_START -->

<!-- TRIAGEBOT_ASSIGN_START -->

<!-- TRIAGEBOT_ASSIGN_DATA_START$${"user":"roife"}$$TRIAGEBOT_ASSIGN_DATA_END -->

<!-- TRIAGEBOT_ASSIGN_END -->
<!-- TRIAGEBOT_END -->

Pull request description:
fix: completions after async kw
fix #17500 

### Changes

1. fix completions after async kw
2. fix completions for `async` kw in trait

Patch:
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index f3ad4e6869..96431ba4ce 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -835,7 +835,11 @@ impl<'a> InferenceContext<'a> {
         let return_ty = if let Some(rpits) = self.db.return_type_impl_traits(func) {
             // RPIT opaque types use substitution of their parent function.
             let fn_placeholders = TyBuilder::placeholder_subst(self.db, func);
-            let result = self.insert_inference_vars_for_impl_trait(return_ty, fn_placeholders);
+            let result = self.insert_inference_vars_for_impl_trait(
+                return_ty,
+                rpits.clone(),
+                fn_placeholders,
+            );
             let rpits = rpits.skip_binders();
             for (id, _) in rpits.impl_traits.iter() {
                 if let Entry::Vacant(e) = self.result.type_of_rpit.entry(id) {
@@ -858,7 +862,12 @@ impl<'a> InferenceContext<'a> {
         self.insert_atpit_coercion_table(params_and_ret_tys.iter());
     }
 
-    fn insert_inference_vars_for_impl_trait<T>(&mut self, t: T, placeholders: Substitution) -> T
+    fn insert_inference_vars_for_impl_trait<T>(
+        &mut self,
+        t: T,
+        rpits: Arc<chalk_ir::Binders<crate::ImplTraits>>,
+        placeholders: Substitution,
+    ) -> T
     where
         T: crate::HasInterner<Interner = Interner> + crate::TypeFoldable<Interner>,
     {
@@ -869,21 +878,13 @@ impl<'a> InferenceContext<'a> {
                     TyKind::OpaqueType(opaque_ty_id, _) => *opaque_ty_id,
                     _ => return ty,
                 };
-                let (impl_traits, idx) =
-                    match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) {
-                        ImplTraitId::ReturnTypeImplTrait(def, idx) => {
-                            (self.db.return_type_impl_traits(def), idx)
-                        }
-                        ImplTraitId::AssociatedTypeImplTrait(def, idx) => {
-                            (self.db.type_alias_impl_traits(def), idx)
-                        }
-                        _ => unreachable!(),
-                    };
-                let Some(impl_traits) = impl_traits else {
-                    return ty;
+                let idx = match self.db.lookup_intern_impl_trait_id(opaque_ty_id.into()) {
+                    ImplTraitId::ReturnTypeImplTrait(_, idx) => idx,
+                    ImplTraitId::AssociatedTypeImplTrait(_, idx) => idx,
+                    _ => unreachable!(),
                 };
-                let bounds = (*impl_traits)
-                    .map_ref(|rpits| rpits.impl_traits[idx].bounds.map_ref(|it| it.iter()));
+                let bounds =
+                    (*rpits).map_ref(|rpits| rpits.impl_traits[idx].bounds.map_ref(|it| it.iter()));
                 let var = self.table.new_type_var();
                 let var_subst = Substitution::from1(Interner, var.clone());
                 for bound in bounds {
@@ -891,8 +892,11 @@ impl<'a> InferenceContext<'a> {
                     let (var_predicate, binders) =
                         predicate.substitute(Interner, &var_subst).into_value_and_skipped_binders();
                     always!(binders.is_empty(Interner)); // quantified where clauses not yet handled
-                    let var_predicate = self
-                        .insert_inference_vars_for_impl_trait(var_predicate, placeholders.clone());
+                    let var_predicate = self.insert_inference_vars_for_impl_trait(
+                        var_predicate,
+                        rpits.clone(),
+                        placeholders.clone(),
+                    );
                     self.push_obligation(var_predicate.cast(Interner));
                 }
                 self.result.type_of_rpit.insert(idx, var.clone());
@@ -979,8 +983,16 @@ impl<'a> InferenceContext<'a> {
                     self.db.lookup_intern_impl_trait_id(opaque_ty_id.into())
                 {
                     if assoc_tys.contains(&alias_id) {
+                        let atpits = self
+                            .db
+                            .type_alias_impl_traits(alias_id)
+                            .expect("Marked as ATPIT but no impl traits!");
                         let alias_placeholders = TyBuilder::placeholder_subst(self.db, alias_id);
-                        let ty = self.insert_inference_vars_for_impl_trait(ty, alias_placeholders);
+                        let ty = self.insert_inference_vars_for_impl_trait(
+                            ty,
+                            atpits,
+                            alias_placeholders,
+                        );
                         return Some((opaque_ty_id, ty));
                     }
                 }
diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs
index 02298b1e9b..1883377408 100644
--- a/crates/ide-completion/src/completions/item_list.rs
+++ b/crates/ide-completion/src/completions/item_list.rs
@@ -82,17 +82,30 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
     let no_vis_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
     let in_block = kind.is_none();
 
+    let missing_qualifiers = [
+        ctx.qualifier_ctx.unsafe_tok.is_none().then_some(("unsafe", "unsafe $0")),
+        ctx.qualifier_ctx.async_tok.is_none().then_some(("async", "async $0")),
+    ];
+
     if !in_trait_impl {
-        if ctx.qualifier_ctx.unsafe_tok.is_some() {
+        // handle qualifier tokens
+        if missing_qualifiers.iter().any(Option::is_none) {
+            // only complete missing qualifiers
+            missing_qualifiers.iter().filter_map(|x| *x).for_each(|(kw, snippet)| {
+                add_keyword(kw, snippet);
+            });
+
             if in_item_list || in_assoc_non_trait_impl {
                 add_keyword("fn", "fn $1($2) {\n    $0\n}");
             }
-            if in_item_list {
+
+            if ctx.qualifier_ctx.unsafe_tok.is_some() && in_item_list {
                 add_keyword("trait", "trait $1 {\n    $0\n}");
                 if no_vis_qualifiers {
                     add_keyword("impl", "impl $1 {\n    $0\n}");
                 }
             }
+
             return;
         }
 
@@ -100,7 +113,6 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
             add_keyword("enum", "enum $1 {\n    $0\n}");
             add_keyword("mod", "mod $0");
             add_keyword("static", "static $0");
-            add_keyword("async", "async $0");
             add_keyword("struct", "struct $0");
             add_keyword("trait", "trait $1 {\n    $0\n}");
             add_keyword("union", "union $1 {\n    $0\n}");
@@ -129,6 +141,7 @@ fn add_keywords(acc: &mut Completions, ctx: &CompletionContext<'_>, kind: Option
             add_keyword("fn", "fn $1($2) {\n    $0\n}");
             add_keyword("unsafe", "unsafe $0");
             add_keyword("const", "const $0");
+            add_keyword("async", "async $0");
         }
     }
 }
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index d79b539882..3f50cd55cb 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -57,6 +57,7 @@ mod tests {
         check(
             r"fn my_fn() { unsafe $0 }",
             expect![[r#"
+                kw async
                 kw fn
                 kw impl
                 kw trait
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 992ca18bb0..5782a4423a 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -46,13 +46,15 @@ pub(crate) enum Visible {
 /// Existing qualifiers for the thing we are currently completing.
 #[derive(Debug, Default)]
 pub(crate) struct QualifierCtx {
+    // TODO: Add try_tok and default_tok
+    pub(crate) async_tok: Option<SyntaxToken>,
     pub(crate) unsafe_tok: Option<SyntaxToken>,
     pub(crate) vis_node: Option<ast::Visibility>,
 }
 
 impl QualifierCtx {
     pub(crate) fn none(&self) -> bool {
-        self.unsafe_tok.is_none() && self.vis_node.is_none()
+        self.async_tok.is_none() && self.unsafe_tok.is_none() && self.vis_node.is_none()
     }
 }
 
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 743aa00570..80dcfd2f52 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -1287,10 +1287,15 @@ fn classify_name_ref(
                 syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
             {
                 if error_node.kind() == SyntaxKind::ERROR {
-                    qualifier_ctx.unsafe_tok = error_node
-                        .children_with_tokens()
-                        .filter_map(NodeOrToken::into_token)
-                        .find(|it| it.kind() == T![unsafe]);
+                    for token in
+                        error_node.children_with_tokens().filter_map(NodeOrToken::into_token)
+                    {
+                        match token.kind() {
+                            SyntaxKind::UNSAFE_KW => qualifier_ctx.unsafe_tok = Some(token),
+                            SyntaxKind::ASYNC_KW => qualifier_ctx.async_tok = Some(token),
+                            _ => {}
+                        }
+                    }
                     qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast);
                 }
             }


Test patch:
diff --git a/crates/hir-ty/src/tests/regression.rs b/crates/hir-ty/src/tests/regression.rs
index 4fcb6062b6..3aa94be755 100644
--- a/crates/hir-ty/src/tests/regression.rs
+++ b/crates/hir-ty/src/tests/regression.rs
@@ -1999,22 +1999,3 @@ where
 "#,
     );
 }
-
-#[test]
-fn tait_async_stack_overflow_17199() {
-    check_types(
-        r#"
-    //- minicore: fmt, future
-    type Foo = impl core::fmt::Debug;
-
-    async fn foo() -> Foo {
-        ()
-    }
-
-    async fn test() {
-        let t = foo().await;
-         // ^ impl Debug
-    }
-"#,
-    );
-}
diff --git a/crates/ide-completion/src/tests/item_list.rs b/crates/ide-completion/src/tests/item_list.rs
index c37900478e..f138938b02 100644
--- a/crates/ide-completion/src/tests/item_list.rs
+++ b/crates/ide-completion/src/tests/item_list.rs
@@ -123,6 +123,7 @@ fn after_unsafe_token() {
     check(
         r#"unsafe $0"#,
         expect![[r#"
+            kw async
             kw fn
             kw impl
             kw trait
@@ -130,6 +131,17 @@ fn after_unsafe_token() {
     );
 }
 
+#[test]
+fn after_async_token() {
+    check(
+        r#"async $0"#,
+        expect![[r#"
+            kw fn
+            kw unsafe
+        "#]],
+    );
+}
+
 #[test]
 fn after_visibility() {
     check(
@@ -157,6 +169,7 @@ fn after_visibility_unsafe() {
     check(
         r#"pub unsafe $0"#,
         expect![[r#"
+            kw async
             kw fn
             kw trait
         "#]],
@@ -170,6 +183,7 @@ fn in_impl_assoc_item_list() {
         expect![[r#"
             ma makro!(…)  macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn
@@ -189,6 +203,7 @@ fn in_impl_assoc_item_list_after_attr() {
         expect![[r#"
             ma makro!(…)  macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn
@@ -208,6 +223,7 @@ fn in_trait_assoc_item_list() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn
@@ -225,6 +241,7 @@ fn in_trait_assoc_fn_missing_body() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn
@@ -242,6 +259,7 @@ fn in_trait_assoc_const_missing_body() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn
@@ -259,6 +277,7 @@ fn in_trait_assoc_type_aliases_missing_ty() {
         expect![[r#"
             ma makro!(…) macro_rules! makro
             md module
+            kw async
             kw const
             kw crate::
             kw fn