about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/tools/rust-analyzer/Cargo.lock98
-rw-r--r--src/tools/rust-analyzer/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/flycheck/Cargo.toml2
-rw-r--r--src/tools/rust-analyzer/crates/flycheck/src/command.rs16
-rw-r--r--src/tools/rust-analyzer/crates/flycheck/src/lib.rs27
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/db.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/files.rs22
-rw-r--r--src/tools/rust-analyzer/crates/hir-expand/src/lib.rs16
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics.rs227
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs20
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/term_search.rs2
-rw-r--r--src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs10
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs685
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs12
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_async_sugar.rs601
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs5
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs51
-rw-r--r--src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs86
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs4
-rw-r--r--src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs14
22 files changed, 1715 insertions, 191 deletions
diff --git a/src/tools/rust-analyzer/Cargo.lock b/src/tools/rust-analyzer/Cargo.lock
index 8eb872514a5..3558c39bb32 100644
--- a/src/tools/rust-analyzer/Cargo.lock
+++ b/src/tools/rust-analyzer/Cargo.lock
@@ -217,16 +217,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "command-group"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916"
-dependencies = [
- "nix 0.26.4",
- "winapi",
-]
-
-[[package]]
 name = "countme"
 version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -292,7 +282,7 @@ version = "3.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345"
 dependencies = [
- "nix 0.28.0",
+ "nix",
  "windows-sys 0.52.0",
 ]
 
@@ -432,9 +422,9 @@ name = "flycheck"
 version = "0.0.0"
 dependencies = [
  "cargo_metadata",
- "command-group",
  "crossbeam-channel",
  "paths",
+ "process-wrap",
  "rustc-hash",
  "serde",
  "serde_json",
@@ -1123,17 +1113,6 @@ dependencies = [
 
 [[package]]
 name = "nix"
-version = "0.26.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
-dependencies = [
- "bitflags 1.3.2",
- "cfg-if",
- "libc",
-]
-
-[[package]]
-name = "nix"
 version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
@@ -1398,6 +1377,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "process-wrap"
+version = "8.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38ee68ae331824036479c84060534b18254c864fa73366c58d86db3b7b811619"
+dependencies = [
+ "indexmap",
+ "nix",
+ "tracing",
+ "windows",
+]
+
+[[package]]
 name = "profile"
 version = "0.0.0"
 dependencies = [
@@ -2375,35 +2366,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
-name = "winapi"
-version = "0.3.9"
+name = "winapi-util"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "windows"
+version = "0.56.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
 dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
+ "windows-core",
+ "windows-targets 0.52.5",
 ]
 
 [[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
+name = "windows-core"
+version = "0.56.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-result",
+ "windows-targets 0.52.5",
+]
 
 [[package]]
-name = "winapi-util"
-version = "0.1.8"
+name = "windows-implement"
+version = "0.56.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
 dependencies = [
- "windows-sys 0.52.0",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
+name = "windows-interface"
+version = "0.56.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b"
+dependencies = [
+ "windows-targets 0.52.5",
+]
 
 [[package]]
 name = "windows-sys"
diff --git a/src/tools/rust-analyzer/Cargo.toml b/src/tools/rust-analyzer/Cargo.toml
index 3108c1b3dfe..ccc27e21333 100644
--- a/src/tools/rust-analyzer/Cargo.toml
+++ b/src/tools/rust-analyzer/Cargo.toml
@@ -111,7 +111,6 @@ chalk-solve = { version = "0.97.0", default-features = false }
 chalk-ir = "0.97.0"
 chalk-recursive = { version = "0.97.0", default-features = false }
 chalk-derive = "0.97.0"
-command-group = "2.0.1"
 crossbeam-channel = "0.5.8"
 dissimilar = "1.0.7"
 dot = "0.1.4"
@@ -132,6 +131,7 @@ object = { version = "0.33.0", default-features = false, features = [
     "macho",
     "pe",
 ] }
+process-wrap = { version = "8.0.2", features = ["std"] }
 pulldown-cmark-to-cmark = "10.0.4"
 pulldown-cmark = { version = "0.9.0", default-features = false }
 rayon = "1.8.0"
diff --git a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
index b8c10da1b6e..d81a5fe3400 100644
--- a/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
+++ b/src/tools/rust-analyzer/crates/flycheck/Cargo.toml
@@ -18,7 +18,7 @@ tracing.workspace = true
 rustc-hash.workspace = true
 serde_json.workspace = true
 serde.workspace = true
-command-group.workspace = true
+process-wrap.workspace = true
 
 # local deps
 paths.workspace = true
diff --git a/src/tools/rust-analyzer/crates/flycheck/src/command.rs b/src/tools/rust-analyzer/crates/flycheck/src/command.rs
index 8ba7018316a..38c7c81f57a 100644
--- a/src/tools/rust-analyzer/crates/flycheck/src/command.rs
+++ b/src/tools/rust-analyzer/crates/flycheck/src/command.rs
@@ -9,8 +9,8 @@ use std::{
     process::{ChildStderr, ChildStdout, Command, Stdio},
 };
 
-use command_group::{CommandGroup, GroupChild};
 use crossbeam_channel::Sender;
+use process_wrap::std::{StdChildWrapper, StdCommandWrap};
 use stdx::process::streaming_output;
 
 /// Cargo output is structured as a one JSON per line. This trait abstracts parsing one line of
@@ -85,7 +85,7 @@ impl<T: ParseFromLine> CargoActor<T> {
     }
 }
 
-struct JodGroupChild(GroupChild);
+struct JodGroupChild(Box<dyn StdChildWrapper>);
 
 impl Drop for JodGroupChild {
     fn drop(&mut self) {
@@ -119,14 +119,20 @@ impl<T> fmt::Debug for CommandHandle<T> {
 impl<T: ParseFromLine> CommandHandle<T> {
     pub(crate) fn spawn(mut command: Command, sender: Sender<T>) -> std::io::Result<Self> {
         command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
-        let mut child = command.group_spawn().map(JodGroupChild)?;
 
         let program = command.get_program().into();
         let arguments = command.get_args().map(|arg| arg.into()).collect::<Vec<OsString>>();
         let current_dir = command.get_current_dir().map(|arg| arg.to_path_buf());
 
-        let stdout = child.0.inner().stdout.take().unwrap();
-        let stderr = child.0.inner().stderr.take().unwrap();
+        let mut child = StdCommandWrap::from(command);
+        #[cfg(unix)]
+        child.wrap(process_wrap::std::ProcessSession);
+        #[cfg(windows)]
+        child.wrap(process_wrap::std::JobObject);
+        let mut child = child.spawn().map(JodGroupChild)?;
+
+        let stdout = child.0.stdout().take().unwrap();
+        let stderr = child.0.stderr().take().unwrap();
 
         let actor = CargoActor::<T>::new(sender, stdout, stderr);
         let thread = stdx::thread::Builder::new(stdx::thread::ThreadIntent::Worker)
diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs
index 6d5ca8321e5..afdc3e389b3 100644
--- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs
@@ -163,6 +163,9 @@ pub enum Message {
     /// Request adding a diagnostic with fixes included to a file
     AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic },
 
+    /// Request clearing all previous diagnostics
+    ClearDiagnostics { id: usize },
+
     /// Request check progress notification to client
     Progress {
         /// Flycheck instance ID
@@ -180,6 +183,9 @@ impl fmt::Debug for Message {
                 .field("workspace_root", workspace_root)
                 .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                 .finish(),
+            Message::ClearDiagnostics { id } => {
+                f.debug_struct("ClearDiagnostics").field("id", id).finish()
+            }
             Message::Progress { id, progress } => {
                 f.debug_struct("Progress").field("id", id).field("progress", progress).finish()
             }
@@ -220,6 +226,8 @@ struct FlycheckActor {
     command_handle: Option<CommandHandle<CargoCheckMessage>>,
     /// The receiver side of the channel mentioned above.
     command_receiver: Option<Receiver<CargoCheckMessage>>,
+
+    status: FlycheckStatus,
 }
 
 enum Event {
@@ -227,6 +235,13 @@ enum Event {
     CheckEvent(Option<CargoCheckMessage>),
 }
 
+#[derive(PartialEq)]
+enum FlycheckStatus {
+    Started,
+    DiagnosticSent,
+    Finished,
+}
+
 const SAVED_FILE_PLACEHOLDER: &str = "$saved_file";
 
 impl FlycheckActor {
@@ -248,6 +263,7 @@ impl FlycheckActor {
             manifest_path,
             command_handle: None,
             command_receiver: None,
+            status: FlycheckStatus::Finished,
         }
     }
 
@@ -298,12 +314,14 @@ impl FlycheckActor {
                             self.command_handle = Some(command_handle);
                             self.command_receiver = Some(receiver);
                             self.report_progress(Progress::DidStart);
+                            self.status = FlycheckStatus::Started;
                         }
                         Err(error) => {
                             self.report_progress(Progress::DidFailToRestart(format!(
                                 "Failed to run the following command: {} error={}",
                                 formatted_command, error
                             )));
+                            self.status = FlycheckStatus::Finished;
                         }
                     }
                 }
@@ -323,7 +341,11 @@ impl FlycheckActor {
                             error
                         );
                     }
+                    if self.status == FlycheckStatus::Started {
+                        self.send(Message::ClearDiagnostics { id: self.id });
+                    }
                     self.report_progress(Progress::DidFinish(res));
+                    self.status = FlycheckStatus::Finished;
                 }
                 Event::CheckEvent(Some(message)) => match message {
                     CargoCheckMessage::CompilerArtifact(msg) => {
@@ -341,11 +363,15 @@ impl FlycheckActor {
                             message = msg.message,
                             "diagnostic received"
                         );
+                        if self.status == FlycheckStatus::Started {
+                            self.send(Message::ClearDiagnostics { id: self.id });
+                        }
                         self.send(Message::AddDiagnostic {
                             id: self.id,
                             workspace_root: self.root.clone(),
                             diagnostic: msg,
                         });
+                        self.status = FlycheckStatus::DiagnosticSent;
                     }
                 },
             }
@@ -362,6 +388,7 @@ impl FlycheckActor {
             );
             command_handle.cancel();
             self.report_progress(Progress::DidCancel);
+            self.status = FlycheckStatus::Finished;
         }
     }
 
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
index 2e5fa6131a7..12421bbe702 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs
@@ -298,7 +298,7 @@ pub fn expand_speculative(
             // prefer tokens of the same kind and text
             // Note the inversion of the score here, as we want to prefer the first token in case
             // of all tokens having the same score
-            (t.kind() != token_to_map.kind()) as u8 + (t.text() != token_to_map.text()) as u8
+            (t.kind() != token_to_map.kind()) as u8 + 2 * ((t.text() != token_to_map.text()) as u8)
         })?;
     Some((node.syntax_node(), token))
 }
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs
index 04a4851ddb7..1ba85c5c7ea 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/files.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/files.rs
@@ -153,24 +153,20 @@ impl<FileId: Copy, N: AstNode> InFileWrapper<FileId, N> {
 // region:specific impls
 
 impl InFile<&SyntaxNode> {
-    /// Skips the attributed item that caused the macro invocation we are climbing up
-    pub fn ancestors_with_macros_skip_attr_item(
+    /// Traverse up macro calls and skips the macro invocation node
+    pub fn ancestors_with_macros(
         self,
         db: &dyn db::ExpandDatabase,
     ) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
         let succ = move |node: &InFile<SyntaxNode>| match node.value.parent() {
             Some(parent) => Some(node.with_value(parent)),
-            None => {
-                let macro_file_id = node.file_id.macro_file()?;
-                let parent_node = macro_file_id.call_node(db);
-                if macro_file_id.is_attr_macro(db) {
-                    // macro call was an attributed item, skip it
-                    // FIXME: does this fail if this is a direct expansion of another macro?
-                    parent_node.map(|node| node.parent()).transpose()
-                } else {
-                    Some(parent_node)
-                }
-            }
+            None => db
+                .lookup_intern_macro_call(node.file_id.macro_file()?.macro_call_id)
+                .to_node_item(db)
+                .syntax()
+                .cloned()
+                .map(|node| node.parent())
+                .transpose(),
         };
         iter::successors(succ(&self.cloned()), succ)
     }
diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
index 4ab989bec2f..83e92565f4d 100644
--- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs
@@ -33,8 +33,8 @@ use std::{fmt, hash::Hash};
 use base_db::{salsa::impl_intern_value_trivial, CrateId, FileId};
 use either::Either;
 use span::{
-    Edition, ErasedFileAstId, FileRange, HirFileIdRepr, Span, SpanAnchor, SyntaxContextData,
-    SyntaxContextId,
+    Edition, ErasedFileAstId, FileAstId, FileRange, HirFileIdRepr, Span, SpanAnchor,
+    SyntaxContextData, SyntaxContextId,
 };
 use syntax::{
     ast::{self, AstNode},
@@ -546,6 +546,18 @@ impl MacroCallLoc {
         }
     }
 
+    pub fn to_node_item(&self, db: &dyn ExpandDatabase) -> InFile<ast::Item> {
+        match self.kind {
+            MacroCallKind::FnLike { ast_id, .. } => {
+                InFile::new(ast_id.file_id, ast_id.map(FileAstId::upcast).to_node(db))
+            }
+            MacroCallKind::Derive { ast_id, .. } => {
+                InFile::new(ast_id.file_id, ast_id.map(FileAstId::upcast).to_node(db))
+            }
+            MacroCallKind::Attr { ast_id, .. } => InFile::new(ast_id.file_id, ast_id.to_node(db)),
+        }
+    }
+
     fn expand_to(&self) -> ExpandTo {
         match self.kind {
             MacroCallKind::FnLike { expand_to, .. } => expand_to,
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
index 6c70cc4baf0..43de2a6ee7d 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs
@@ -380,6 +380,27 @@ impl<'db> SemanticsImpl<'db> {
         self.with_ctx(|ctx| ctx.has_derives(adt))
     }
 
+    pub fn derive_helper(&self, attr: &ast::Attr) -> Option<Vec<(Macro, MacroFileId)>> {
+        let adt = attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+            ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+            ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+            ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+            _ => None,
+        })?;
+        let attr_name = attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+        let sa = self.analyze_no_infer(adt.syntax())?;
+        let id = self.db.ast_id_map(sa.file_id).ast_id(&adt);
+        let res: Vec<_> = sa
+            .resolver
+            .def_map()
+            .derive_helpers_in_scope(InFile::new(sa.file_id, id))?
+            .iter()
+            .filter(|&(name, _, _)| *name == attr_name)
+            .map(|&(_, macro_, call)| (macro_.into(), call.as_macro_file()))
+            .collect();
+        res.is_empty().not().then_some(res)
+    }
+
     pub fn is_attr_macro_call(&self, item: &ast::Item) -> bool {
         let file_id = self.find_file(item.syntax()).file_id;
         let src = InFile::new(file_id, item.clone());
@@ -409,6 +430,20 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    pub fn speculative_expand_raw(
+        &self,
+        macro_file: MacroFileId,
+        speculative_args: &SyntaxNode,
+        token_to_map: SyntaxToken,
+    ) -> Option<(SyntaxNode, SyntaxToken)> {
+        hir_expand::db::expand_speculative(
+            self.db.upcast(),
+            macro_file.macro_call_id,
+            speculative_args,
+            token_to_map,
+        )
+    }
+
     /// Expand the macro call with a different item as the input, mapping the `token_to_map` down into the
     /// expansion. `token_to_map` should be a token from the `speculative args` node.
     pub fn speculative_expand_attr_macro(
@@ -826,107 +861,109 @@ impl<'db> SemanticsImpl<'db> {
 
                     // Then check for token trees, that means we are either in a function-like macro or
                     // secondary attribute inputs
-                    let tt = token.parent_ancestors().map_while(ast::TokenTree::cast).last()?;
-                    let parent = tt.syntax().parent()?;
-
-                    if tt.left_delimiter_token().map_or(false, |it| it == token) {
-                        return None;
-                    }
-                    if tt.right_delimiter_token().map_or(false, |it| it == token) {
-                        return None;
-                    }
-
-                    if let Some(macro_call) = ast::MacroCall::cast(parent.clone()) {
-                        let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
-                            InFile::new(file_id, macro_call);
-                        let file_id = match mcache.get(&mcall) {
-                            Some(&it) => it,
-                            None => {
-                                let it = sa.expand(self.db, mcall.as_ref())?;
-                                mcache.insert(mcall, it);
-                                it
+                    let tt = token
+                        .parent_ancestors()
+                        .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+                        .last()?;
+                    match tt {
+                        Either::Left(tt) => {
+                            if tt.left_delimiter_token().map_or(false, |it| it == token) {
+                                return None;
                             }
-                        };
-                        let text_range = tt.syntax().text_range();
-                        // remove any other token in this macro input, all their mappings are the
-                        // same as this one
-                        tokens.retain(|t| !text_range.contains_range(t.text_range()));
-
-                        process_expansion_for_token(&mut stack, file_id).or(file_id
-                            .eager_arg(self.db.upcast())
-                            .and_then(|arg| {
-                                // also descend into eager expansions
-                                process_expansion_for_token(&mut stack, arg.as_macro_file())
-                            }))
-                    } else if let Some(meta) = ast::Meta::cast(parent) {
-                        // attribute we failed expansion for earlier, this might be a derive invocation
-                        // or derive helper attribute
-                        let attr = meta.parent_attr()?;
-
-                        let adt = if let Some(adt) = attr.syntax().parent().and_then(ast::Adt::cast)
-                        {
-                            // this might be a derive, or a derive helper on an ADT
-                            let derive_call = self.with_ctx(|ctx| {
-                                // so try downmapping the token into the pseudo derive expansion
-                                // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
-                                ctx.attr_to_derive_macro_call(
-                                    InFile::new(file_id, &adt),
-                                    InFile::new(file_id, attr.clone()),
-                                )
-                                .map(|(_, call_id, _)| call_id)
-                            });
-
-                            match derive_call {
-                                Some(call_id) => {
-                                    // resolved to a derive
-                                    let file_id = call_id.as_macro_file();
-                                    let text_range = attr.syntax().text_range();
-                                    // remove any other token in this macro input, all their mappings are the
-                                    // same as this one
-                                    tokens.retain(|t| !text_range.contains_range(t.text_range()));
-                                    return process_expansion_for_token(&mut stack, file_id);
+                            if tt.right_delimiter_token().map_or(false, |it| it == token) {
+                                return None;
+                            }
+                            let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
+                            let mcall: hir_expand::files::InFileWrapper<HirFileId, ast::MacroCall> =
+                                InFile::new(file_id, macro_call);
+                            let file_id = match mcache.get(&mcall) {
+                                Some(&it) => it,
+                                None => {
+                                    let it = sa.expand(self.db, mcall.as_ref())?;
+                                    mcache.insert(mcall, it);
+                                    it
+                                }
+                            };
+                            let text_range = tt.syntax().text_range();
+                            // remove any other token in this macro input, all their mappings are the
+                            // same as this one
+                            tokens.retain(|t| !text_range.contains_range(t.text_range()));
+
+                            process_expansion_for_token(&mut stack, file_id).or(file_id
+                                .eager_arg(self.db.upcast())
+                                .and_then(|arg| {
+                                    // also descend into eager expansions
+                                    process_expansion_for_token(&mut stack, arg.as_macro_file())
+                                }))
+                        }
+                        Either::Right(meta) => {
+                            // attribute we failed expansion for earlier, this might be a derive invocation
+                            // or derive helper attribute
+                            let attr = meta.parent_attr()?;
+                            let adt = match attr.syntax().parent().and_then(ast::Adt::cast) {
+                                Some(adt) => {
+                                    // this might be a derive on an ADT
+                                    let derive_call = self.with_ctx(|ctx| {
+                                        // so try downmapping the token into the pseudo derive expansion
+                                        // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works
+                                        ctx.attr_to_derive_macro_call(
+                                            InFile::new(file_id, &adt),
+                                            InFile::new(file_id, attr.clone()),
+                                        )
+                                        .map(|(_, call_id, _)| call_id)
+                                    });
+
+                                    match derive_call {
+                                        Some(call_id) => {
+                                            // resolved to a derive
+                                            let file_id = call_id.as_macro_file();
+                                            let text_range = attr.syntax().text_range();
+                                            // remove any other token in this macro input, all their mappings are the
+                                            // same as this
+                                            tokens.retain(|t| {
+                                                !text_range.contains_range(t.text_range())
+                                            });
+                                            return process_expansion_for_token(
+                                                &mut stack, file_id,
+                                            );
+                                        }
+                                        None => Some(adt),
+                                    }
+                                }
+                                None => {
+                                    // Otherwise this could be a derive helper on a variant or field
+                                    attr.syntax().ancestors().find_map(ast::Item::cast).and_then(
+                                        |it| match it {
+                                            ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                                            ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                                            ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                                            _ => None,
+                                        },
+                                    )
                                 }
-                                None => Some(adt),
+                            }?;
+                            if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
+                                return None;
                             }
-                        } else {
-                            // Otherwise this could be a derive helper on a variant or field
-                            if let Some(field) =
-                                attr.syntax().parent().and_then(ast::RecordField::cast)
+                            let attr_name =
+                                attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
+                            // Not an attribute, nor a derive, so it's either a builtin or a derive helper
+                            // Try to resolve to a derive helper and downmap
+                            let id = self.db.ast_id_map(file_id).ast_id(&adt);
+                            let helpers =
+                                def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
+
+                            let mut res = None;
+                            for (.., derive) in
+                                helpers.iter().filter(|(helper, ..)| *helper == attr_name)
                             {
-                                field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
-                            } else if let Some(field) =
-                                attr.syntax().parent().and_then(ast::TupleField::cast)
-                            {
-                                field.syntax().ancestors().take(4).find_map(ast::Adt::cast)
-                            } else if let Some(variant) =
-                                attr.syntax().parent().and_then(ast::Variant::cast)
-                            {
-                                variant.syntax().ancestors().nth(2).and_then(ast::Adt::cast)
-                            } else {
-                                None
+                                res = res.or(process_expansion_for_token(
+                                    &mut stack,
+                                    derive.as_macro_file(),
+                                ));
                             }
-                        }?;
-                        if !self.with_ctx(|ctx| ctx.has_derives(InFile::new(file_id, &adt))) {
-                            return None;
-                        }
-                        // Not an attribute, nor a derive, so it's either a builtin or a derive helper
-                        // Try to resolve to a derive helper and downmap
-                        let attr_name =
-                            attr.path().and_then(|it| it.as_single_name_ref())?.as_name();
-                        let id = self.db.ast_id_map(file_id).ast_id(&adt);
-                        let helpers = def_map.derive_helpers_in_scope(InFile::new(file_id, id))?;
-                        let mut res = None;
-                        for (.., derive) in
-                            helpers.iter().filter(|(helper, ..)| *helper == attr_name)
-                        {
-                            res = res.or(process_expansion_for_token(
-                                &mut stack,
-                                derive.as_macro_file(),
-                            ));
+                            res
                         }
-                        res
-                    } else {
-                        None
                     }
                 })()
                 .is_none();
diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
index d2bd8b0e799..77e7cdb58ab 100644
--- a/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/semantics/source_to_def.rs
@@ -139,7 +139,7 @@ impl SourceToDefCtx<'_, '_> {
         let _p = tracing::span!(tracing::Level::INFO, "module_to_def").entered();
         let parent_declaration = src
             .syntax()
-            .ancestors_with_macros_skip_attr_item(self.db.upcast())
+            .ancestors_with_macros(self.db.upcast())
             .find_map(|it| it.map(Either::<ast::Module, ast::BlockExpr>::cast).transpose())
             .map(|it| it.transpose());
 
@@ -366,7 +366,7 @@ impl SourceToDefCtx<'_, '_> {
     }
 
     pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
-        for container in src.ancestors_with_macros_skip_attr_item(self.db.upcast()) {
+        for container in src.ancestors_with_macros(self.db.upcast()) {
             if let Some(res) = self.container_to_def(container) {
                 return Some(res);
             }
@@ -420,7 +420,7 @@ impl SourceToDefCtx<'_, '_> {
     }
 
     fn find_generic_param_container(&mut self, src: InFile<&SyntaxNode>) -> Option<GenericDefId> {
-        let ancestors = src.ancestors_with_macros_skip_attr_item(self.db.upcast());
+        let ancestors = src.ancestors_with_macros(self.db.upcast());
         for InFile { file_id, value } in ancestors {
             let item = match ast::Item::cast(value) {
                 Some(it) => it,
@@ -429,6 +429,7 @@ impl SourceToDefCtx<'_, '_> {
             let res: GenericDefId = match item {
                 ast::Item::Fn(it) => self.fn_to_def(InFile::new(file_id, it))?.into(),
                 ast::Item::Struct(it) => self.struct_to_def(InFile::new(file_id, it))?.into(),
+                ast::Item::Union(it) => self.union_to_def(InFile::new(file_id, it))?.into(),
                 ast::Item::Enum(it) => self.enum_to_def(InFile::new(file_id, it))?.into(),
                 ast::Item::Trait(it) => self.trait_to_def(InFile::new(file_id, it))?.into(),
                 ast::Item::TraitAlias(it) => {
@@ -446,11 +447,18 @@ impl SourceToDefCtx<'_, '_> {
     }
 
     fn find_pat_or_label_container(&mut self, src: InFile<&SyntaxNode>) -> Option<DefWithBodyId> {
-        let ancestors = src.ancestors_with_macros_skip_attr_item(self.db.upcast());
+        let ancestors = src.ancestors_with_macros(self.db.upcast());
         for InFile { file_id, value } in ancestors {
-            let item = match ast::Item::cast(value) {
+            let item = match ast::Item::cast(value.clone()) {
                 Some(it) => it,
-                None => continue,
+                None => {
+                    if let Some(variant) = ast::Variant::cast(value.clone()) {
+                        return self
+                            .enum_variant_to_def(InFile::new(file_id, variant))
+                            .map(Into::into);
+                    }
+                    continue;
+                }
             };
             let res: DefWithBodyId = match item {
                 ast::Item::Const(it) => self.const_to_def(InFile::new(file_id, it))?.into(),
diff --git a/src/tools/rust-analyzer/crates/hir/src/term_search.rs b/src/tools/rust-analyzer/crates/hir/src/term_search.rs
index 5c5ddae19e2..7b70cdf4599 100644
--- a/src/tools/rust-analyzer/crates/hir/src/term_search.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/term_search.rs
@@ -329,7 +329,7 @@ pub fn term_search<DB: HirDatabase>(ctx: &TermSearchCtx<'_, DB>) -> Vec<Expr> {
     while should_continue() {
         lookup.new_round();
 
-        solutions.extend(tactics::type_constructor(ctx, &defs, &mut lookup, should_continue));
+        solutions.extend(tactics::data_constructor(ctx, &defs, &mut lookup, should_continue));
         solutions.extend(tactics::free_function(ctx, &defs, &mut lookup, should_continue));
         solutions.extend(tactics::impl_method(ctx, &defs, &mut lookup, should_continue));
         solutions.extend(tactics::struct_projection(ctx, &defs, &mut lookup, should_continue));
diff --git a/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs b/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs
index a26728272dc..f95ff1dc0fa 100644
--- a/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs
+++ b/src/tools/rust-analyzer/crates/hir/src/term_search/tactics.rs
@@ -87,9 +87,9 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
     })
 }
 
-/// # Type constructor tactic
+/// # Data constructor tactic
 ///
-/// Attempts different type constructors for enums and structs in scope
+/// Attempts different data constructors for enums and structs in scope
 ///
 /// Updates lookup by new types reached and returns iterator that yields
 /// elements that unify with `goal`.
@@ -99,7 +99,7 @@ pub(super) fn trivial<'a, DB: HirDatabase>(
 /// * `defs` - Set of items in scope at term search target location
 /// * `lookup` - Lookup table for types
 /// * `should_continue` - Function that indicates when to stop iterating
-pub(super) fn type_constructor<'a, DB: HirDatabase>(
+pub(super) fn data_constructor<'a, DB: HirDatabase>(
     ctx: &'a TermSearchCtx<'a, DB>,
     defs: &'a FxHashSet<ScopeDef>,
     lookup: &'a mut LookupTable,
@@ -308,7 +308,9 @@ pub(super) fn type_constructor<'a, DB: HirDatabase>(
                         // Early exit if some param cannot be filled from lookup
                         let param_exprs: Vec<Vec<Expr>> = fields
                             .into_iter()
-                            .map(|field| lookup.find(db, &field.ty(db)))
+                            .map(|field| {
+                                lookup.find(db, &field.ty_with_args(db, generics.iter().cloned()))
+                            })
                             .collect::<Option<_>>()?;
 
                         // Note that we need special case for 0 param constructors because of multi cartesian
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs
new file mode 100644
index 00000000000..953119fd1ff
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs
@@ -0,0 +1,685 @@
+use itertools::Itertools;
+use syntax::{
+    ast::{self, edit::IndentLevel, Comment, CommentPlacement, Whitespace},
+    AstToken, Direction, SyntaxElement, TextRange,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: comment_to_doc
+//
+// Converts comments to documentation.
+//
+// ```
+// // Wow what $0a nice module
+// // I sure hope this shows up when I hover over it
+// ```
+// ->
+// ```
+// //! Wow what a nice module
+// //! I sure hope this shows up when I hover over it
+// ```
+pub(crate) fn convert_comment_from_or_to_doc(
+    acc: &mut Assists,
+    ctx: &AssistContext<'_>,
+) -> Option<()> {
+    let comment = ctx.find_token_at_offset::<ast::Comment>()?;
+
+    match comment.kind().doc {
+        Some(_) => doc_to_comment(acc, comment),
+        None => can_be_doc_comment(&comment).and_then(|style| comment_to_doc(acc, comment, style)),
+    }
+}
+
+fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
+    let target = if comment.kind().shape.is_line() {
+        line_comments_text_range(&comment)?
+    } else {
+        comment.syntax().text_range()
+    };
+
+    acc.add(
+        AssistId("doc_to_comment", AssistKind::RefactorRewrite),
+        "Replace comment with doc comment",
+        target,
+        |edit| {
+            // We need to either replace the first occurrence of /* with /***, or we need to replace
+            // the occurrences // at the start of each line with ///
+            let output = match comment.kind().shape {
+                ast::CommentShape::Line => {
+                    let indentation = IndentLevel::from_token(comment.syntax());
+                    let line_start = comment.prefix();
+                    let prefix = format!("{indentation}//");
+                    relevant_line_comments(&comment)
+                        .iter()
+                        .map(|comment| comment.text())
+                        .flat_map(|text| text.lines())
+                        .map(|line| line.replacen(line_start, &prefix, 1))
+                        .join("\n")
+                }
+                ast::CommentShape::Block => {
+                    let block_start = comment.prefix();
+                    comment
+                        .text()
+                        .lines()
+                        .enumerate()
+                        .map(|(idx, line)| {
+                            if idx == 0 {
+                                line.replacen(block_start, "/*", 1)
+                            } else {
+                                line.replacen("*  ", "* ", 1)
+                            }
+                        })
+                        .join("\n")
+                }
+            };
+            edit.replace(target, output)
+        },
+    )
+}
+
+fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacement) -> Option<()> {
+    let target = if comment.kind().shape.is_line() {
+        line_comments_text_range(&comment)?
+    } else {
+        comment.syntax().text_range()
+    };
+
+    acc.add(
+        AssistId("comment_to_doc", AssistKind::RefactorRewrite),
+        "Replace doc comment with comment",
+        target,
+        |edit| {
+            // We need to either replace the first occurrence of /* with /***, or we need to replace
+            // the occurrences // at the start of each line with ///
+            let output = match comment.kind().shape {
+                ast::CommentShape::Line => {
+                    let indentation = IndentLevel::from_token(comment.syntax());
+                    let line_start = match style {
+                        CommentPlacement::Inner => format!("{indentation}//!"),
+                        CommentPlacement::Outer => format!("{indentation}///"),
+                    };
+                    relevant_line_comments(&comment)
+                        .iter()
+                        .map(|comment| comment.text())
+                        .flat_map(|text| text.lines())
+                        .map(|line| line.replacen("//", &line_start, 1))
+                        .join("\n")
+                }
+                ast::CommentShape::Block => {
+                    let block_start = match style {
+                        CommentPlacement::Inner => "/*!",
+                        CommentPlacement::Outer => "/**",
+                    };
+                    comment
+                        .text()
+                        .lines()
+                        .enumerate()
+                        .map(|(idx, line)| {
+                            if idx == 0 {
+                                // On the first line we replace the comment start with a doc comment
+                                // start.
+                                line.replacen("/*", block_start, 1)
+                            } else {
+                                // put one extra space after each * since we moved the first line to
+                                // the right by one column as well.
+                                line.replacen("* ", "*  ", 1)
+                            }
+                        })
+                        .join("\n")
+                }
+            };
+            edit.replace(target, output)
+        },
+    )
+}
+
+/// Not all comments are valid candidates for conversion into doc comments. For example, the
+/// comments in the code:
+/// ```rust
+/// // Brilliant module right here
+///
+/// // Really good right
+/// fn good_function(foo: Foo) -> Bar {
+///     foo.into_bar()
+/// }
+///
+/// // So nice
+/// mod nice_module {}
+/// ```
+/// can be converted to doc comments. However, the comments in this example:
+/// ```rust
+/// fn foo_bar(foo: Foo /* not bar yet */) -> Bar {
+///     foo.into_bar()
+///     // Nicely done
+/// }
+/// // end of function
+///
+/// struct S {
+///     // The S struct
+/// }
+/// ```
+/// are not allowed to become doc comments. Moreover, some comments _are_ allowed, but aren't common
+/// style in Rust. For example, the following comments are allowed to be doc comments, but it is not
+/// common style for them to be:
+/// ```rust
+/// fn foo_bar(foo: Foo) -> Bar {
+///     // this could be an inner comment with //!
+///     foo.into_bar()
+/// }
+///
+/// trait T {
+///     // The T struct could also be documented from within
+/// }
+///
+/// mod mymod {
+///     // Modules only normally get inner documentation when they are defined as a separate file.
+/// }
+/// ```
+fn can_be_doc_comment(comment: &ast::Comment) -> Option<CommentPlacement> {
+    use syntax::SyntaxKind::*;
+
+    // if the comment is not on its own line, then we do not propose anything.
+    match comment.syntax().prev_token() {
+        Some(prev) => {
+            // There was a previous token, now check if it was a newline
+            Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
+        }
+        // There is no previous token, this is the start of the file.
+        None => return Some(CommentPlacement::Inner),
+    }
+
+    // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`,
+    // `use` or `const`.
+    let parent = comment.syntax().parent();
+    let par_kind = parent.as_ref().map(|parent| parent.kind());
+    matches!(par_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_ALIAS | EXTERN_CRATE | USE | CONST))
+        .then_some(CommentPlacement::Outer)
+}
+
+/// The line -> block assist can  be invoked from anywhere within a sequence of line comments.
+/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
+/// be joined.
+pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
+    // The prefix identifies the kind of comment we're dealing with
+    let prefix = comment.prefix();
+    let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
+
+    // These tokens are allowed to exist between comments
+    let skippable = |not: &SyntaxElement| {
+        not.clone()
+            .into_token()
+            .and_then(Whitespace::cast)
+            .map(|w| !w.spans_multiple_lines())
+            .unwrap_or(false)
+    };
+
+    // Find all preceding comments (in reverse order) that have the same prefix
+    let prev_comments = comment
+        .syntax()
+        .siblings_with_tokens(Direction::Prev)
+        .filter(|s| !skippable(s))
+        .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+        .take_while(|opt_com| opt_com.is_some())
+        .flatten()
+        .skip(1); // skip the first element so we don't duplicate it in next_comments
+
+    let next_comments = comment
+        .syntax()
+        .siblings_with_tokens(Direction::Next)
+        .filter(|s| !skippable(s))
+        .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix))
+        .take_while(|opt_com| opt_com.is_some())
+        .flatten();
+
+    let mut comments: Vec<_> = prev_comments.collect();
+    comments.reverse();
+    comments.extend(next_comments);
+    comments
+}
+
+fn line_comments_text_range(comment: &ast::Comment) -> Option<TextRange> {
+    let comments = relevant_line_comments(comment);
+    let first = comments.first()?;
+    let indentation = IndentLevel::from_token(first.syntax());
+    let start =
+        first.syntax().text_range().start().checked_sub((indentation.0 as u32 * 4).into())?;
+    let end = comments.last()?.syntax().text_range().end();
+    Some(TextRange::new(start, end))
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn module_comment_to_doc() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            // such a nice module$0
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+            //! such a nice module
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_line_comment_to_doc() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            // unseen$0 docs
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /// unseen docs
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_line_comment_to_doc() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            // unseen$0 docs
+            // make me seen!
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /// unseen docs
+            /// make me seen!
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_line_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /// visible$0 docs
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            // visible docs
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_line_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /// visible$0 docs
+            /// Hide me!
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            // visible docs
+            // Hide me!
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_line_block_comment_to_doc() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /* unseen$0 docs */
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /** unseen docs */
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_line_block_comment_to_doc() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /* unseen$0 docs
+            *  make me seen!
+            */
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /** unseen docs
+            *   make me seen!
+            */
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_line_block_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /** visible$0 docs */
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /* visible docs */
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_line_block_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+
+            /** visible$0 docs
+            *   Hide me!
+            */
+            fn main() {
+                foo();
+            }
+            "#,
+            r#"
+
+            /* visible docs
+            *  Hide me!
+            */
+            fn main() {
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_inner_line_comment_to_doc() {
+        check_assist_not_applicable(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                // unseen$0 docs
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_inner_line_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                //! visible$0 docs
+                foo();
+            }
+            "#,
+            r#"
+            mod mymod {
+                // visible docs
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_inner_line_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                //! visible$0 docs
+                //! Hide me!
+                foo();
+            }
+            "#,
+            r#"
+            mod mymod {
+                // visible docs
+                // Hide me!
+                foo();
+            }
+            "#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                /// visible$0 docs
+                /// Hide me!
+                foo();
+            }
+            "#,
+            r#"
+            mod mymod {
+                // visible docs
+                // Hide me!
+                foo();
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn single_inner_line_block_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                /*! visible$0 docs */
+                type Int = i32;
+            }
+            "#,
+            r#"
+            mod mymod {
+                /* visible docs */
+                type Int = i32;
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn multi_inner_line_block_doc_to_comment() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                /*! visible$0 docs
+                *   Hide me!
+                */
+                type Int = i32;
+            }
+            "#,
+            r#"
+            mod mymod {
+                /* visible docs
+                *  Hide me!
+                */
+                type Int = i32;
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn not_overeager() {
+        check_assist_not_applicable(
+            convert_comment_from_or_to_doc,
+            r#"
+            fn main() {
+                foo();
+                // $0well that settles main
+            }
+            // $1 nicely done
+            "#,
+        );
+    }
+
+    #[test]
+    fn all_possible_items() {
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice struct$0 */
+                struct S {}
+            }"#,
+            r#"mod m {
+                /** Nice struct */
+                struct S {}
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice trait$0 */
+                trait T {}
+            }"#,
+            r#"mod m {
+                /** Nice trait */
+                trait T {}
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice module$0 */
+                mod module {}
+            }"#,
+            r#"mod m {
+                /** Nice module */
+                mod module {}
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice function$0 */
+                fn function() {}
+            }"#,
+            r#"mod m {
+                /** Nice function */
+                fn function() {}
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice type$0 */
+                type Type Int = i32;
+            }"#,
+            r#"mod m {
+                /** Nice type */
+                type Type Int = i32;
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice crate$0 */
+                extern crate rust_analyzer;
+            }"#,
+            r#"mod m {
+                /** Nice crate */
+                extern crate rust_analyzer;
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice import$0 */
+                use ide_assists::convert_comment_from_or_to_doc::tests
+            }"#,
+            r#"mod m {
+                /** Nice import */
+                use ide_assists::convert_comment_from_or_to_doc::tests
+            }"#,
+        );
+        check_assist(
+            convert_comment_from_or_to_doc,
+            r#"mod m {
+                /* Nice constant$0 */
+                const CONST: &str = "very const";
+            }"#,
+            r#"mod m {
+                /** Nice constant */
+                const CONST: &str = "very const";
+            }"#,
+        );
+    }
+
+    #[test]
+    fn no_inner_comments() {
+        check_assist_not_applicable(
+            convert_comment_from_or_to_doc,
+            r#"
+            mod mymod {
+                // aaa$0aa
+            }
+            "#,
+        );
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs
index ffd1508ccbd..94e0519cba0 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/term_search.rs
@@ -278,4 +278,16 @@ fn f() { let a = 1; let b = 0.0; let c: (i32, (i32, f64)) = todo$0!(); }"#,
             r#"fn f() { let a = 1; let b = 0.0; let c: (i32, (i32, f64)) = (a, (a, b)); }"#,
         )
     }
+
+    #[test]
+    fn test_tuple_struct_with_generics() {
+        check_assist(
+            term_search,
+            r#"//- minicore: todo, unimplemented
+struct Foo<T>(T);
+fn f() { let a = 1; let b: Foo<i32> = todo$0!(); }"#,
+            r#"struct Foo<T>(T);
+fn f() { let a = 1; let b: Foo<i32> = Foo(a); }"#,
+        )
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_async_sugar.rs
new file mode 100644
index 00000000000..30e09648ea1
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/toggle_async_sugar.rs
@@ -0,0 +1,601 @@
+use hir::{ImportPathConfig, ModuleDef};
+use ide_db::{
+    assists::{AssistId, AssistKind},
+    famous_defs::FamousDefs,
+};
+use syntax::{
+    ast::{self, HasVisibility},
+    AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
+};
+
+use crate::{AssistContext, Assists};
+
+// Assist: sugar_impl_future_into_async
+//
+// Rewrites asynchronous function from `-> impl Future` into `async fn`.
+// This action does not touch the function body and therefore `async { 0 }`
+// block does not transform to just `0`.
+//
+// ```
+// # //- minicore: future
+// pub fn foo() -> impl core::future::F$0uture<Output = usize> {
+//     async { 0 }
+// }
+// ```
+// ->
+// ```
+// pub async fn foo() -> usize {
+//     async { 0 }
+// }
+// ```
+pub(crate) fn sugar_impl_future_into_async(
+    acc: &mut Assists,
+    ctx: &AssistContext<'_>,
+) -> Option<()> {
+    let ret_type: ast::RetType = ctx.find_node_at_offset()?;
+    let function = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
+
+    if function.async_token().is_some() || function.const_token().is_some() {
+        return None;
+    }
+
+    let ast::Type::ImplTraitType(return_impl_trait) = ret_type.ty()? else {
+        return None;
+    };
+
+    let main_trait_path = return_impl_trait
+        .type_bound_list()?
+        .bounds()
+        .filter_map(|bound| match bound.ty() {
+            Some(ast::Type::PathType(trait_path)) => trait_path.path(),
+            _ => None,
+        })
+        .next()?;
+
+    let trait_type = ctx.sema.resolve_trait(&main_trait_path)?;
+    let scope = ctx.sema.scope(main_trait_path.syntax())?;
+    if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_future_Future()? {
+        return None;
+    }
+    let future_output = unwrap_future_output(main_trait_path)?;
+
+    acc.add(
+        AssistId("sugar_impl_future_into_async", AssistKind::RefactorRewrite),
+        "Convert `impl Future` into async",
+        function.syntax().text_range(),
+        |builder| {
+            match future_output {
+                // Empty tuple
+                ast::Type::TupleType(t) if t.fields().next().is_none() => {
+                    let mut ret_type_range = ret_type.syntax().text_range();
+
+                    // find leftover whitespace
+                    let whitespace_range = function
+                        .param_list()
+                        .as_ref()
+                        .map(|params| NodeOrToken::Node(params.syntax()))
+                        .and_then(following_whitespace);
+
+                    if let Some(whitespace_range) = whitespace_range {
+                        ret_type_range =
+                            TextRange::new(whitespace_range.start(), ret_type_range.end());
+                    }
+
+                    builder.delete(ret_type_range);
+                }
+                _ => {
+                    builder.replace(
+                        return_impl_trait.syntax().text_range(),
+                        future_output.syntax().text(),
+                    );
+                }
+            }
+
+            let (place_for_async, async_kw) = match function.visibility() {
+                Some(vis) => (vis.syntax().text_range().end(), " async"),
+                None => (function.syntax().text_range().start(), "async "),
+            };
+            builder.insert(place_for_async, async_kw);
+        },
+    )
+}
+
+// Assist: desugar_async_into_impl_future
+//
+// Rewrites asynchronous function from `async fn` into `-> impl Future`.
+// This action does not touch the function body and therefore `0`
+// block does not transform to `async { 0 }`.
+//
+// ```
+// # //- minicore: future
+// pub as$0ync fn foo() -> usize {
+//     0
+// }
+// ```
+// ->
+// ```
+// pub fn foo() -> impl core::future::Future<Output = usize> {
+//     0
+// }
+// ```
+pub(crate) fn desugar_async_into_impl_future(
+    acc: &mut Assists,
+    ctx: &AssistContext<'_>,
+) -> Option<()> {
+    let async_token = ctx.find_token_syntax_at_offset(SyntaxKind::ASYNC_KW)?;
+    let function = async_token.parent().and_then(ast::Fn::cast)?;
+
+    let rparen = function.param_list()?.r_paren_token()?;
+    let return_type = match function.ret_type() {
+        // unable to get a `ty` makes the action unapplicable
+        Some(ret_type) => Some(ret_type.ty()?),
+        // No type means `-> ()`
+        None => None,
+    };
+
+    let scope = ctx.sema.scope(function.syntax())?;
+    let module = scope.module();
+    let future_trait = FamousDefs(&ctx.sema, scope.krate()).core_future_Future()?;
+    let trait_path = module.find_path(
+        ctx.db(),
+        ModuleDef::Trait(future_trait),
+        ImportPathConfig {
+            prefer_no_std: ctx.config.prefer_no_std,
+            prefer_prelude: ctx.config.prefer_prelude,
+        },
+    )?;
+    let trait_path = trait_path.display(ctx.db());
+
+    acc.add(
+        AssistId("desugar_async_into_impl_future", AssistKind::RefactorRewrite),
+        "Convert async into `impl Future`",
+        function.syntax().text_range(),
+        |builder| {
+            let mut async_range = async_token.text_range();
+
+            if let Some(whitespace_range) = following_whitespace(NodeOrToken::Token(async_token)) {
+                async_range = TextRange::new(async_range.start(), whitespace_range.end());
+            }
+            builder.delete(async_range);
+
+            match return_type {
+                Some(ret_type) => builder.replace(
+                    ret_type.syntax().text_range(),
+                    format!("impl {trait_path}<Output = {ret_type}>"),
+                ),
+                None => builder.insert(
+                    rparen.text_range().end(),
+                    format!(" -> impl {trait_path}<Output = ()>"),
+                ),
+            }
+        },
+    )
+}
+
+fn unwrap_future_output(path: ast::Path) -> Option<ast::Type> {
+    let future_trait = path.segments().last()?;
+    let assoc_list = future_trait.generic_arg_list()?;
+    let future_assoc = assoc_list.generic_args().next()?;
+    match future_assoc {
+        ast::GenericArg::AssocTypeArg(output_type) => output_type.ty(),
+        _ => None,
+    }
+}
+
+fn following_whitespace(nt: NodeOrToken<&SyntaxNode, SyntaxToken>) -> Option<TextRange> {
+    let next_token = match nt {
+        NodeOrToken::Node(node) => node.next_sibling_or_token(),
+        NodeOrToken::Token(token) => token.next_sibling_or_token(),
+    }?;
+    (next_token.kind() == SyntaxKind::WHITESPACE).then_some(next_token.text_range())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    #[test]
+    fn sugar_with_use() {
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    fn foo() -> impl F$0uture<Output = ()> {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future::Future;
+    async fn foo() {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    fn foo() -> impl F$0uture<Output = usize> {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future::Future;
+    async fn foo() -> usize {
+        todo!()
+    }
+    "#,
+        );
+    }
+
+    #[test]
+    fn desugar_with_use() {
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    as$0ync fn foo() {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future::Future;
+    fn foo() -> impl Future<Output = ()> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    use core::future;
+    as$0ync fn foo() {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future;
+    fn foo() -> impl future::Future<Output = ()> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    as$0ync fn foo() -> usize {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future::Future;
+    fn foo() -> impl Future<Output = usize> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    as$0ync fn foo() -> impl Future<Output = usize> {
+        todo!()
+    }
+    "#,
+            r#"
+    use core::future::Future;
+    fn foo() -> impl Future<Output = impl Future<Output = usize>> {
+        todo!()
+    }
+    "#,
+        );
+    }
+
+    #[test]
+    fn sugar_without_use() {
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = ()> {
+        todo!()
+    }
+    "#,
+            r#"
+    async fn foo() {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = usize> {
+        todo!()
+    }
+    "#,
+            r#"
+    async fn foo() -> usize {
+        todo!()
+    }
+    "#,
+        );
+    }
+
+    #[test]
+    fn desugar_without_use() {
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    as$0ync fn foo() {
+        todo!()
+    }
+    "#,
+            r#"
+    fn foo() -> impl core::future::Future<Output = ()> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist(
+            desugar_async_into_impl_future,
+            r#"
+    //- minicore: future
+    as$0ync fn foo() -> usize {
+        todo!()
+    }
+    "#,
+            r#"
+    fn foo() -> impl core::future::Future<Output = usize> {
+        todo!()
+    }
+    "#,
+        );
+    }
+
+    #[test]
+    fn not_applicable() {
+        check_assist_not_applicable(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    trait Future {
+        type Output;
+    }
+    fn foo() -> impl F$0uture<Output = ()> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist_not_applicable(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    trait Future {
+        type Output;
+    }
+    fn foo() -> impl F$0uture<Output = usize> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist_not_applicable(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    f$0n foo() -> impl core::future::Future<Output = usize> {
+        todo!()
+    }
+    "#,
+        );
+
+        check_assist_not_applicable(
+            desugar_async_into_impl_future,
+            r#"
+    async f$0n foo() {
+        todo!()
+    }
+    "#,
+        );
+    }
+
+    #[test]
+    fn sugar_definition_with_use() {
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    fn foo() -> impl F$0uture<Output = ()>;
+    "#,
+            r#"
+    use core::future::Future;
+    async fn foo();
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    use core::future::Future;
+    fn foo() -> impl F$0uture<Output = usize>;
+    "#,
+            r#"
+    use core::future::Future;
+    async fn foo() -> usize;
+    "#,
+        );
+    }
+
+    #[test]
+    fn sugar_definition_without_use() {
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = ()>;
+    "#,
+            r#"
+    async fn foo();
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = usize>;
+    "#,
+            r#"
+    async fn foo() -> usize;
+    "#,
+        );
+    }
+
+    #[test]
+    fn sugar_more_types() {
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = ()> + Send + Sync;
+    "#,
+            r#"
+    async fn foo();
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = usize> + Debug;
+    "#,
+            r#"
+    async fn foo() -> usize;
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = (usize)> + Debug;
+    "#,
+            r#"
+    async fn foo() -> (usize);
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::F$0uture<Output = (usize, usize)> + Debug;
+    "#,
+            r#"
+    async fn foo() -> (usize, usize);
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo() -> impl core::future::Future<Output = impl core::future::F$0uture<Output = ()> + Send>;
+    "#,
+            r#"
+    async fn foo() -> impl core::future::Future<Output = ()> + Send;
+    "#,
+        );
+    }
+
+    #[test]
+    fn sugar_with_modifiers() {
+        check_assist_not_applicable(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    const fn foo() -> impl core::future::F$0uture<Output = ()>;
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+            //- minicore: future
+            pub(crate) unsafe fn foo() -> impl core::future::F$0uture<Output = usize>;
+        "#,
+            r#"
+            pub(crate) async unsafe fn foo() -> usize;
+        "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    unsafe fn foo() -> impl core::future::F$0uture<Output = ()>;
+    "#,
+            r#"
+    async unsafe fn foo();
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    unsafe extern "C" fn foo() -> impl core::future::F$0uture<Output = ()>;
+    "#,
+            r#"
+    async unsafe extern "C" fn foo();
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo<T>() -> impl core::future::F$0uture<Output = T>;
+    "#,
+            r#"
+    async fn foo<T>() -> T;
+    "#,
+        );
+
+        check_assist(
+            sugar_impl_future_into_async,
+            r#"
+    //- minicore: future
+    fn foo<T>() -> impl core::future::F$0uture<Output = T>
+    where
+        T: Sized;
+    "#,
+            r#"
+    async fn foo<T>() -> T
+    where
+        T: Sized;
+    "#,
+        );
+    }
+}
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
index 0df5e913a57..abebdec1d18 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -116,6 +116,7 @@ mod handlers {
     mod change_visibility;
     mod convert_bool_then;
     mod convert_comment_block;
+    mod convert_comment_from_or_to_doc;
     mod convert_from_to_tryfrom;
     mod convert_integer_literal;
     mod convert_into_to_from;
@@ -209,6 +210,7 @@ mod handlers {
     mod sort_items;
     mod split_import;
     mod term_search;
+    mod toggle_async_sugar;
     mod toggle_ignore;
     mod unmerge_match_arm;
     mod unmerge_use;
@@ -238,7 +240,10 @@ mod handlers {
             change_visibility::change_visibility,
             convert_bool_then::convert_bool_then_to_if,
             convert_bool_then::convert_if_to_bool_then,
+            toggle_async_sugar::desugar_async_into_impl_future,
+            toggle_async_sugar::sugar_impl_future_into_async,
             convert_comment_block::convert_comment_block,
+            convert_comment_from_or_to_doc::convert_comment_from_or_to_doc,
             convert_from_to_tryfrom::convert_from_to_tryfrom,
             convert_integer_literal::convert_integer_literal,
             convert_into_to_from::convert_into_to_from,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index 937e78f8d7d..eec1087e2df 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -346,6 +346,21 @@ pub(crate) fn frobnicate() {}
 }
 
 #[test]
+fn doctest_comment_to_doc() {
+    check_doc_test(
+        "comment_to_doc",
+        r#####"
+// Wow what $0a nice module
+// I sure hope this shows up when I hover over it
+"#####,
+        r#####"
+//! Wow what a nice module
+//! I sure hope this shows up when I hover over it
+"#####,
+    )
+}
+
+#[test]
 fn doctest_convert_bool_then_to_if() {
     check_doc_test(
         "convert_bool_then_to_if",
@@ -801,6 +816,24 @@ fn main() {
 }
 
 #[test]
+fn doctest_desugar_async_into_impl_future() {
+    check_doc_test(
+        "desugar_async_into_impl_future",
+        r#####"
+//- minicore: future
+pub as$0ync fn foo() -> usize {
+    0
+}
+"#####,
+        r#####"
+pub fn foo() -> impl core::future::Future<Output = usize> {
+    0
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_desugar_doc_comment() {
     check_doc_test(
         "desugar_doc_comment",
@@ -3021,6 +3054,24 @@ use std::{collections::HashMap};
 }
 
 #[test]
+fn doctest_sugar_impl_future_into_async() {
+    check_doc_test(
+        "sugar_impl_future_into_async",
+        r#####"
+//- minicore: future
+pub fn foo() -> impl core::future::F$0uture<Output = usize> {
+    async { 0 }
+}
+"#####,
+        r#####"
+pub async fn foo() -> usize {
+    async { 0 }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_toggle_ignore() {
     check_doc_test(
         "toggle_ignore",
diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
index 79c503e0a10..f0c6e7a63b0 100644
--- a/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
+++ b/src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs
@@ -3,8 +3,9 @@ use std::iter;
 
 use hir::{Semantics, Type, TypeInfo, Variant};
 use ide_db::{active_parameter::ActiveParameter, RootDatabase};
+use itertools::Either;
 use syntax::{
-    algo::{find_node_at_offset, non_trivia_sibling},
+    algo::{ancestors_at_offset, find_node_at_offset, non_trivia_sibling},
     ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
     match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
     SyntaxToken, TextRange, TextSize, T,
@@ -119,20 +120,45 @@ fn expand(
         }
 
         // No attributes have been expanded, so look for macro_call! token trees or derive token trees
-        let orig_tt = match find_node_at_offset::<ast::TokenTree>(&original_file, offset) {
+        let orig_tt = match ancestors_at_offset(&original_file, offset)
+            .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+            .last()
+        {
             Some(it) => it,
             None => break 'expansion,
         };
-        let spec_tt = match find_node_at_offset::<ast::TokenTree>(&speculative_file, offset) {
+        let spec_tt = match ancestors_at_offset(&speculative_file, offset)
+            .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
+            .last()
+        {
             Some(it) => it,
             None => break 'expansion,
         };
 
-        // Expand pseudo-derive expansion
-        if let (Some(orig_attr), Some(spec_attr)) = (
-            orig_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
-            spec_tt.syntax().parent().and_then(ast::Meta::cast).and_then(|it| it.parent_attr()),
-        ) {
+        let (tts, attrs) = match (orig_tt, spec_tt) {
+            (Either::Left(orig_tt), Either::Left(spec_tt)) => {
+                let attrs = orig_tt
+                    .syntax()
+                    .parent()
+                    .and_then(ast::Meta::cast)
+                    .and_then(|it| it.parent_attr())
+                    .zip(
+                        spec_tt
+                            .syntax()
+                            .parent()
+                            .and_then(ast::Meta::cast)
+                            .and_then(|it| it.parent_attr()),
+                    );
+                (Some((orig_tt, spec_tt)), attrs)
+            }
+            (Either::Right(orig_path), Either::Right(spec_path)) => {
+                (None, orig_path.parent_attr().zip(spec_path.parent_attr()))
+            }
+            _ => break 'expansion,
+        };
+
+        // Expand pseudo-derive expansion aka `derive(Debug$0)`
+        if let Some((orig_attr, spec_attr)) = attrs {
             if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_token))) = (
                 sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
                 sema.speculative_expand_derive_as_pseudo_attr_macro(
@@ -147,15 +173,54 @@ fn expand(
                     fake_mapped_token.text_range().start(),
                     orig_attr,
                 ));
+                break 'expansion;
+            }
+
+            if let Some(spec_adt) =
+                spec_attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
+                    ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
+                    ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
+                    ast::Item::Union(it) => Some(ast::Adt::Union(it)),
+                    _ => None,
+                })
+            {
+                // might be the path of derive helper or a token tree inside of one
+                if let Some(helpers) = sema.derive_helper(&orig_attr) {
+                    for (_mac, file) in helpers {
+                        if let Some((fake_expansion, fake_mapped_token)) = sema
+                            .speculative_expand_raw(
+                                file,
+                                spec_adt.syntax(),
+                                fake_ident_token.clone(),
+                            )
+                        {
+                            // we are inside a derive helper token tree, treat this as being inside
+                            // the derive expansion
+                            let actual_expansion = sema.parse_or_expand(file.into());
+                            let new_offset = fake_mapped_token.text_range().start();
+                            if new_offset + relative_offset > actual_expansion.text_range().end() {
+                                // offset outside of bounds from the original expansion,
+                                // stop here to prevent problems from happening
+                                break 'expansion;
+                            }
+                            original_file = actual_expansion;
+                            speculative_file = fake_expansion;
+                            fake_ident_token = fake_mapped_token;
+                            offset = new_offset;
+                            continue 'expansion;
+                        }
+                    }
+                }
             }
             // at this point we won't have any more successful expansions, so stop
             break 'expansion;
         }
 
         // Expand fn-like macro calls
+        let Some((orig_tt, spec_tt)) = tts else { break 'expansion };
         if let (Some(actual_macro_call), Some(macro_call_with_fake_ident)) = (
-            orig_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
-            spec_tt.syntax().ancestors().find_map(ast::MacroCall::cast),
+            orig_tt.syntax().parent().and_then(ast::MacroCall::cast),
+            spec_tt.syntax().parent().and_then(ast::MacroCall::cast),
         ) {
             let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
             let mac_call_path1 =
@@ -201,6 +266,7 @@ fn expand(
         // none of our states have changed so stop the loop
         break 'expansion;
     }
+
     ExpansionResult { original_file, speculative_file, offset, fake_ident_token, derive_ctx }
 }
 
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
index 3106772e63b..e445e9fb68d 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/famous_defs.rs
@@ -106,6 +106,10 @@ impl FamousDefs<'_, '_> {
         self.find_trait("core:marker:Copy")
     }
 
+    pub fn core_future_Future(&self) -> Option<Trait> {
+        self.find_trait("core:future:Future")
+    }
+
     pub fn core_macros_builtin_derive(&self) -> Option<Macro> {
         self.find_macro("core:macros:builtin:derive")
     }
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 fab62e95d19..c19f19803f1 100644
--- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
+++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs
@@ -16,7 +16,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
+// - trait assoc items will navigate to the assoc item of the trait declaration as 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,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
index 79b87ecd58f..f64e66183d1 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/global_state.rs
@@ -87,7 +87,6 @@ pub(crate) struct GlobalState {
     pub(crate) flycheck_sender: Sender<flycheck::Message>,
     pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
     pub(crate) last_flycheck_error: Option<String>,
-    pub(crate) diagnostics_received: bool,
 
     // Test explorer
     pub(crate) test_run_session: Option<Vec<flycheck::CargoTestHandle>>,
@@ -225,7 +224,6 @@ impl GlobalState {
             flycheck_sender,
             flycheck_receiver,
             last_flycheck_error: None,
-            diagnostics_received: false,
 
             test_run_session: None,
             test_run_sender,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
index 7acd302867c..193b3fdd4a8 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs
@@ -804,10 +804,6 @@ impl GlobalState {
     fn handle_flycheck_msg(&mut self, message: flycheck::Message) {
         match message {
             flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => {
-                if !self.diagnostics_received {
-                    self.diagnostics.clear_check(id);
-                    self.diagnostics_received = true;
-                }
                 let snap = self.snapshot();
                 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
                     &self.config.diagnostics_map(),
@@ -833,12 +829,11 @@ impl GlobalState {
                 }
             }
 
+            flycheck::Message::ClearDiagnostics { id } => self.diagnostics.clear_check(id),
+
             flycheck::Message::Progress { id, progress } => {
                 let (state, message) = match progress {
-                    flycheck::Progress::DidStart => {
-                        self.diagnostics_received = false;
-                        (Progress::Begin, None)
-                    }
+                    flycheck::Progress::DidStart => (Progress::Begin, None),
                     flycheck::Progress::DidCheckCrate(target) => (Progress::Report, Some(target)),
                     flycheck::Progress::DidCancel => {
                         self.last_flycheck_error = None;
@@ -852,9 +847,6 @@ impl GlobalState {
                     flycheck::Progress::DidFinish(result) => {
                         self.last_flycheck_error =
                             result.err().map(|err| format!("cargo check failed to start: {err}"));
-                        if !self.diagnostics_received {
-                            self.diagnostics.clear_check(id);
-                        }
                         (Progress::End, None)
                     }
                 };