Repository: rust-lang/rust-analyzer
Pull request #17094 (2024-04-17 19:35:27)

feat: Add convert From to TryFrom assist
Adds a new code assist to convert a `From` impl into a `TryFrom` impl.

This is useful in situations where it turns out after or halfway through writing a `From` implementation that the conversion is actually fallible.

## Example

https://github.com/rust-lang/rust-analyzer/assets/26360861/872ec7c4-c9ff-451c-9453-4baaaad47326


Veykril: One minor thing, thanks!
@bors delegate+
bors: :v: @Lindronics, you can now approve this pull request!

If @Veykril told you to "`r=me`" after making some further change, please make that change, then do `@bors r=@Veykril`
<!-- homu: {"type":"Delegated","delegator":"Veykril","delegate":"Lindronics"} -->

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index da65b034be..0000000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Contributing to rust-analyzer
-
-Thank you for your interest in contributing to rust-analyzer! There are many ways to contribute
-and we appreciate all of them.
-
-To get a quick overview of the crates and structure of the project take a look at the
-[./docs/dev](./docs/dev) folder.
-
-If you have any questions please ask them in the [rust-analyzer zulip stream](
-https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Frust-analyzer) or if unsure where
-to start out when working on a concrete issue drop a comment on the related issue for mentoring
-instructions (general discussions are recommended to happen on zulip though).
-
-## Fixing a bug or improving a feature
-
-Generally it's fine to just work on these kinds of things and put a pull-request out for it. If there
-is an issue accompanying it make sure to link it in the pull request description so it can be closed
-afterwards or linked for context.
-
-If you want to find something to fix or work on keep a look out for the `C-bug` and `C-enhancement`
-labels.
-
-## Implementing a new feature
-
-It's advised to first open an issue for any kind of new feature so the team can tell upfront whether
-the feature is desirable or not before any implementation work happens. We want to minimize the
-possibility of someone putting a lot of work into a feature that is then going to waste as we deem
-it out of scope (be it due to generally not fitting in with rust-analyzer, or just not having the
-maintenance capacity). If there already is a feature issue open but it is not clear whether it is
-considered accepted feel free to just drop a comment and ask!
diff --git a/README.md b/README.md
index 552f71f151..8c3f6f8468 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,8 @@ https://rust-analyzer.github.io/manual.html#installation
 
 ## Documentation
 
-If you want to **contribute** to rust-analyzer check out the [CONTRIBUTING.md](./CONTRIBUTING.md) or
-if you are just curious about how things work under the hood, check the [./docs/dev](./docs/dev)
-folder.
+If you want to **contribute** to rust-analyzer or are just curious about how
+things work under the hood, check the [./docs/dev](./docs/dev) folder.
 
 If you want to **use** rust-analyzer's language server with your editor of
 choice, check [the manual](https://rust-analyzer.github.io/manual.html) folder.
diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs
index d7233a8923..97fa9cf2cc 100644
--- a/crates/hir-expand/src/db.rs
+++ b/crates/hir-expand/src/db.rs
@@ -3,7 +3,7 @@
 use base_db::{salsa, CrateId, FileId, SourceDatabase};
 use either::Either;
 use limit::Limit;
-use mbe::{syntax_node_to_token_tree, MatchedArmIndex};
+use mbe::syntax_node_to_token_tree;
 use rustc_hash::FxHashSet;
 use span::{AstIdMap, Span, SyntaxContextData, SyntaxContextId};
 use syntax::{ast, AstNode, Parse, SyntaxElement, SyntaxError, SyntaxNode, SyntaxToken, T};
@@ -313,10 +313,9 @@ fn parse_macro_expansion(
     let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
     let edition = loc.def.edition;
     let expand_to = loc.expand_to();
-    let mbe::ValueResult { value: (tt, matched_arm), err } =
-        macro_expand(db, macro_file.macro_call_id, loc);
+    let mbe::ValueResult { value: tt, err } = macro_expand(db, macro_file.macro_call_id, loc);
 
-    let (parse, mut rev_token_map) = token_tree_to_syntax_node(
+    let (parse, rev_token_map) = token_tree_to_syntax_node(
         match &tt {
             CowArc::Arc(it) => it,
             CowArc::Owned(it) => it,
@@ -324,7 +323,6 @@ fn parse_macro_expansion(
         expand_to,
         edition,
     );
-    rev_token_map.matched_arm = matched_arm;
 
     ExpandResult { value: (parse, Arc::new(rev_token_map)), err }
 }
@@ -546,13 +544,11 @@ fn macro_expand(
     db: &dyn ExpandDatabase,
     macro_call_id: MacroCallId,
     loc: MacroCallLoc,
-) -> ExpandResult<(CowArc<tt::Subtree>, MatchedArmIndex)> {
+) -> ExpandResult<CowArc<tt::Subtree>> {
     let _p = tracing::span!(tracing::Level::INFO, "macro_expand").entered();
 
-    let (ExpandResult { value: (tt, matched_arm), err }, span) = match loc.def.kind {
-        MacroDefKind::ProcMacro(..) => {
-            return db.expand_proc_macro(macro_call_id).map(CowArc::Arc).zip_val(None)
-        }
+    let (ExpandResult { value: tt, err }, span) = match loc.def.kind {
+        MacroDefKind::ProcMacro(..) => return db.expand_proc_macro(macro_call_id).map(CowArc::Arc),
         _ => {
             let (macro_arg, undo_info, span) =
                 db.macro_arg_considering_derives(macro_call_id, &loc.kind);
@@ -564,10 +560,10 @@ fn macro_expand(
                         .decl_macro_expander(loc.def.krate, id)
                         .expand(db, arg.clone(), macro_call_id, span),
                     MacroDefKind::BuiltIn(it, _) => {
-                        it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
+                        it.expand(db, macro_call_id, arg, span).map_err(Into::into)
                     }
                     MacroDefKind::BuiltInDerive(it, _) => {
-                        it.expand(db, macro_call_id, arg, span).map_err(Into::into).zip_val(None)
+                        it.expand(db, macro_call_id, arg, span).map_err(Into::into)
                     }
                     MacroDefKind::BuiltInEager(it, _) => {
                         // This might look a bit odd, but we do not expand the inputs to eager macros here.
@@ -578,8 +574,7 @@ fn macro_expand(
                         // As such we just return the input subtree here.
                         let eager = match &loc.kind {
                             MacroCallKind::FnLike { eager: None, .. } => {
-                                return ExpandResult::ok(CowArc::Arc(macro_arg.clone()))
-                                    .zip_val(None);
+                                return ExpandResult::ok(CowArc::Arc(macro_arg.clone()));
                             }
                             MacroCallKind::FnLike { eager: Some(eager), .. } => Some(&**eager),
                             _ => None,
@@ -591,12 +586,12 @@ fn macro_expand(
                             // FIXME: We should report both errors!
                             res.err = error.clone().or(res.err);
                         }
-                        res.zip_val(None)
+                        res
                     }
                     MacroDefKind::BuiltInAttr(it, _) => {
                         let mut res = it.expand(db, macro_call_id, arg, span);
                         fixup::reverse_fixups(&mut res.value, &undo_info);
-                        res.zip_val(None)
+                        res
                     }
                     _ => unreachable!(),
                 };
@@ -608,18 +603,16 @@ fn macro_expand(
     if !loc.def.is_include() {
         // Set a hard limit for the expanded tt
         if let Err(value) = check_tt_count(&tt) {
-            return value
-                .map(|()| {
-                    CowArc::Owned(tt::Subtree {
-                        delimiter: tt::Delimiter::invisible_spanned(span),
-                        token_trees: Box::new([]),
-                    })
+            return value.map(|()| {
+                CowArc::Owned(tt::Subtree {
+                    delimiter: tt::Delimiter::invisible_spanned(span),
+                    token_trees: Box::new([]),
                 })
-                .zip_val(matched_arm);
+            });
         }
     }
 
-    ExpandResult { value: (CowArc::Owned(tt), matched_arm), err }
+    ExpandResult { value: CowArc::Owned(tt), err }
 }
 
 fn proc_macro_span(db: &dyn ExpandDatabase, ast: AstId<ast::Fn>) -> Span {
diff --git a/crates/hir-expand/src/declarative.rs b/crates/hir-expand/src/declarative.rs
index 66465ce600..f9ea8e2ea5 100644
--- a/crates/hir-expand/src/declarative.rs
+++ b/crates/hir-expand/src/declarative.rs
@@ -3,7 +3,6 @@ use std::sync::OnceLock;
 
 use base_db::{CrateId, VersionReq};
 use span::{Edition, MacroCallId, Span, SyntaxContextId};
-use stdx::TupleExt;
 use syntax::{ast, AstNode};
 use triomphe::Arc;
 
@@ -31,7 +30,7 @@ impl DeclarativeMacroExpander {
         tt: tt::Subtree,
         call_id: MacroCallId,
         span: Span,
-    ) -> ExpandResult<(tt::Subtree, Option<u32>)> {
+    ) -> ExpandResult<tt::Subtree> {
         let loc = db.lookup_intern_macro_call(call_id);
         let toolchain = db.toolchain(loc.def.krate);
         let new_meta_vars = toolchain.as_ref().map_or(false, |version| {
@@ -47,7 +46,7 @@ impl DeclarativeMacroExpander {
         });
         match self.mac.err() {
             Some(_) => ExpandResult::new(
-                (tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), None),
+                tt::Subtree::empty(tt::DelimSpan { open: span, close: span }),
                 ExpandError::MacroDefinition,
             ),
             None => self
@@ -91,7 +90,6 @@ impl DeclarativeMacroExpander {
             None => self
                 .mac
                 .expand(&tt, |_| (), new_meta_vars, call_site, def_site_edition)
-                .map(TupleExt::head)
                 .map_err(Into::into),
         }
     }
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index e792e159ac..e1516fcdcb 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1246,17 +1246,6 @@ impl<'db> SemanticsImpl<'db> {
             .map_or(false, |m| matches!(m.id, MacroId::ProcMacroId(..)))
     }
 
-    pub fn resolve_macro_call_arm(&self, macro_call: &ast::MacroCall) -> Option<u32> {
-        let sa = self.analyze(macro_call.syntax())?;
-        self.db
-            .parse_macro_expansion(
-                sa.expand(self.db, self.wrap_node_infile(macro_call.clone()).as_ref())?,
-            )
-            .value
-            .1
-            .matched_arm
-    }
-
     pub fn is_unsafe_macro_call(&self, macro_call: &ast::MacroCall) -> bool {
         let sa = match self.analyze(macro_call.syntax()) {
             Some(it) => it,
diff --git a/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs b/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs
new file mode 100644
index 0000000000..5459bd334c
--- /dev/null
+++ b/crates/ide-assists/src/handlers/convert_from_to_tryfrom.rs
@@ -0,0 +1,250 @@
+use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait};
+use itertools::Itertools;
+use syntax::{
+    ast::{self, make, AstNode, HasName},
+    ted,
+};
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: convert_from_to_tryfrom
+//
+// Converts a From impl to a TryFrom impl, wrapping returns in `Ok`.
+//
+// ```
+// # //- minicore: from
+// impl $0From<usize> for Thing {
+//     fn from(val: usize) -> Self {
+//         Thing {
+//             b: val.to_string(),
+//             a: val
+//         }
+//     }
+// }
+// ```
+// ->
+// ```
+// impl TryFrom<usize> for Thing {
+//     type Error = ${0:()};
+//
+//     fn try_from(val: usize) -> Result<Self, Self::Error> {
+//         Ok(Thing {
+//             b: val.to_string(),
+//             a: val
+//         })
+//     }
+// }
+// ```
+pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let impl_ = ctx.find_node_at_offset::<ast::Impl>()?;
+    let trait_ty = impl_.trait_()?;
+
+    let module = ctx.sema.scope(impl_.syntax())?.module();
+
+    let from_type = match &trait_ty {
+        ast::Type::PathType(path) => {
+            path.path()?.segment()?.generic_arg_list()?.generic_args().next()?
+        }
+        _ => return None,
+    };
+
+    let associated_items = impl_.assoc_item_list()?;
+    let from_fn = associated_items.assoc_items().find_map(|item| {
+        if let ast::AssocItem::Fn(f) = item {
+            if f.name()?.text() == "from" {
+                return Some(f);
+            }
+        };
+        None
+    })?;
+
+    let from_fn_name = from_fn.name()?;
+    let from_fn_return_type = from_fn.ret_type()?.ty()?;
+
+    let return_exprs = from_fn.body()?.syntax().descendants().filter_map(ast::ReturnExpr::cast);
+    let tail_expr = from_fn.body()?.tail_expr()?;
+
+    if resolve_target_trait(&ctx.sema, &impl_)?
+        != FamousDefs(&ctx.sema, module.krate()).core_convert_From()?
+    {
+        return None;
+    }
+
+    acc.add(
+        AssistId("convert_from_to_tryfrom", AssistKind::RefactorRewrite),
+        "Convert From to TryFrom",
+        impl_.syntax().text_range(),
+        |builder| {
+            let trait_ty = builder.make_mut(trait_ty);
+            let from_fn_return_type = builder.make_mut(from_fn_return_type);
+            let from_fn_name = builder.make_mut(from_fn_name);
+            let tail_expr = builder.make_mut(tail_expr);
+            let return_exprs = return_exprs.map(|r| builder.make_mut(r)).collect_vec();
+            let associated_items = builder.make_mut(associated_items).clone();
+
+            ted::replace(
+                trait_ty.syntax(),
+                make::ty(&format!("TryFrom<{from_type}>")).syntax().clone_for_update(),
+            );
+            ted::replace(
+                from_fn_return_type.syntax(),
+                make::ty("Result<Self, Self::Error>").syntax().clone_for_update(),
+            );
+            ted::replace(from_fn_name.syntax(), make::name("try_from").syntax().clone_for_update());
+            ted::replace(
+                tail_expr.syntax(),
+                wrap_ok(tail_expr.clone()).syntax().clone_for_update(),
+            );
+
+            for r in return_exprs {
+                let t = r.expr().unwrap_or_else(make::expr_unit);
+                ted::replace(t.syntax(), wrap_ok(t.clone()).syntax().clone_for_update());
+            }
+
+            let error_type = ast::AssocItem::TypeAlias(make::ty_alias(
+                "Error",
+                None,
+                None,
+                None,
+                Some((make::ty_unit(), None)),
+            ))
+            .clone_for_update();
+
+            if let Some(cap) = ctx.config.snippet_cap {
+                if let ast::AssocItem::TypeAlias(type_alias) = &error_type {
+                    if let Some(ty) = type_alias.ty() {
+                        builder.add_placeholder_snippet(cap, ty);
+                    }
+                }
+            }
+
+            associated_items.add_item_at_start(error_type);
+        },
+    )
+}
+
+fn wrap_ok(expr: ast::Expr) -> ast::Expr {
+    make::expr_call(
+        make::expr_path(make::ext::ident_path("Ok")),
+        make::arg_list(std::iter::once(expr)),
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    #[test]
+    fn converts_from_to_tryfrom() {
+        check_assist(
+            convert_from_to_tryfrom,
+            r#"
+//- minicore: from
+struct Foo(String);
+
+impl $0From<String> for Foo {
+    fn from(val: String) -> Self {
+        if val == "bar" {
+            return Foo(val);
+        }
+        Self(val)
+    }
+}
+            "#,
+            r#"
+struct Foo(String);
+
+impl TryFrom<String> for Foo {
+    type Error = ${0:()};
+
+    fn try_from(val: String) -> Result<Self, Self::Error> {
+        if val == "bar" {
+            return Ok(Foo(val));
+        }
+        Ok(Self(val))
+    }
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn converts_from_to_tryfrom_nested_type() {
+        check_assist(
+            convert_from_to_tryfrom,
+            r#"
+//- minicore: from
+struct Foo(String);
+
+impl $0From<Option<String>> for Foo {
+    fn from(val: Option<String>) -> Self {
+        match val {
+            Some(val) => Foo(val),
+            None => Foo("".to_string())
+        }
+    }
+}
+            "#,
+            r#"
+struct Foo(String);
+
+impl TryFrom<Option<String>> for Foo {
+    type Error = ${0:()};
+
+    fn try_from(val: Option<String>) -> Result<Self, Self::Error> {
+        Ok(match val {
+            Some(val) => Foo(val),
+            None => Foo("".to_string())
+        })
+    }
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn converts_from_to_tryfrom_preserves_lifetimes() {
+        check_assist(
+            convert_from_to_tryfrom,
+            r#"
+//- minicore: from
+struct Foo<'a>(&'a str);
+
+impl<'a> $0From<&'a str> for Foo<'a> {
+    fn from(val: &'a str) -> Self {
+        Self(val)
+    }
+}
+            "#,
+            r#"
+struct Foo<'a>(&'a str);
+
+impl<'a> TryFrom<&'a str> for Foo<'a> {
+    type Error = ${0:()};
+
+    fn try_from(val: &'a str) -> Result<Self, Self::Error> {
+        Ok(Self(val))
+    }
+}
+            "#,
+        );
+    }
+
+    #[test]
+    fn other_trait_not_applicable() {
+        check_assist_not_applicable(
+            convert_from_to_tryfrom,
+            r#"
+struct Foo(String);
+
+impl $0TryFrom<String> for Foo {
+    fn try_from(val: String) -> Result<Self, Self::Error> {
+        Ok(Self(val))
+    }
+}
+            "#,
+        );
+    }
+}
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 024448ef1e..0df5e913a5 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/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_from_to_tryfrom;
     mod convert_integer_literal;
     mod convert_into_to_from;
     mod convert_iter_for_each_to_for;
@@ -238,6 +239,7 @@ mod handlers {
             convert_bool_then::convert_bool_then_to_if,
             convert_bool_then::convert_if_to_bool_then,
             convert_comment_block::convert_comment_block,
+            convert_from_to_tryfrom::convert_from_to_tryfrom,
             convert_integer_literal::convert_integer_literal,
             convert_into_to_from::convert_into_to_from,
             convert_iter_for_each_to_for::convert_iter_for_each_to_for,
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 95de3c88c8..822751c0e4 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use ide_db::{
     helpers::pick_best_token,
     FxIndexSet, RootDatabase,
 };
-use itertools::{multizip, Itertools};
+use itertools::Itertools;
 use syntax::{ast, match_ast, AstNode, AstToken, SyntaxKind::*, SyntaxNode, T};
 
 use crate::{
@@ -149,7 +149,7 @@ fn hover_simple(
     if let Some(doc_comment) = token_as_doc_comment(&original_token) {
         cov_mark::hit!(no_highlight_on_comment_hover);
         return doc_comment.get_definition_with_descend_at(sema, offset, |def, node, range| {
-            let res = hover_for_definition(sema, file_id, def, &node, None, config);
+            let res = hover_for_definition(sema, file_id, def, &node, config);
             Some(RangeInfo::new(range, res))
         });
     }
@@ -162,7 +162,6 @@ fn hover_simple(
             file_id,
             Definition::from(resolution?),
             &original_token.parent()?,
-            None,
             config,
         );
         return Some(RangeInfo::new(range, res));
@@ -197,29 +196,6 @@ fn hover_simple(
             descended()
                 .filter_map(|token| {
                     let node = token.parent()?;
-
-                    // special case macro calls, we wanna render the invoked arm index
-                    if let Some(name) = ast::NameRef::cast(node.clone()) {
-                        if let Some(path_seg) =
-                            name.syntax().parent().and_then(ast::PathSegment::cast)
-                        {
-                            if let Some(macro_call) = path_seg
-                                .parent_path()
-                                .syntax()
-                                .parent()
-                                .and_then(ast::MacroCall::cast)
-                            {
-                                if let Some(macro_) = sema.resolve_macro_call(&macro_call) {
-                                    return Some(vec![(
-                                        Definition::Macro(macro_),
-                                        sema.resolve_macro_call_arm(&macro_call),
-                                        node,
-                                    )]);
-                                }
-                            }
-                        }
-                    }
-
                     match IdentClass::classify_node(sema, &node)? {
                         // It's better for us to fall back to the keyword hover here,
                         // rendering poll is very confusing
@@ -228,19 +204,20 @@ fn hover_simple(
                         IdentClass::NameRefClass(NameRefClass::ExternCrateShorthand {
                             decl,
                             ..
-                        }) => Some(vec![(Definition::ExternCrateDecl(decl), None, node)]),
+                        }) => Some(vec![(Definition::ExternCrateDecl(decl), node)]),
 
                         class => Some(
-                            multizip((class.definitions(), iter::repeat(None), iter::repeat(node)))
+                            class
+                                .definitions()
+                                .into_iter()
+                                .zip(iter::repeat(node))
                                 .collect::<Vec<_>>(),
                         ),
                     }
                 })
                 .flatten()
-                .unique_by(|&(def, _, _)| def)
-                .map(|(def, macro_arm, node)| {
-                    hover_for_definition(sema, file_id, def, &node, macro_arm, config)
-                })
+                .unique_by(|&(def, _)| def)
+                .map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
                 .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
                     acc.actions.extend(actions);
                     acc.markup = Markup::from(format!("{}\n---\n{markup}", acc.markup));
@@ -397,7 +374,6 @@ pub(crate) fn hover_for_definition(
     file_id: FileId,
     def: Definition,
     scope_node: &SyntaxNode,
-    macro_arm: Option<u32>,
     config: &HoverConfig,
 ) -> HoverResult {
     let famous_defs = match &def {
@@ -422,8 +398,7 @@ pub(crate) fn hover_for_definition(
     };
     let notable_traits = def_ty.map(|ty| notable_traits(db, &ty)).unwrap_or_default();
 
-    let markup =
-        render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, macro_arm, config);
+    let markup = render::definition(sema.db, def, famous_defs.as_ref(), &notable_traits, config);
     HoverResult {
         markup: render::process_markup(sema.db, def, &markup, config),
         actions: [
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index 3f0fc85134..7579d4d6d8 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -403,7 +403,6 @@ pub(super) fn definition(
     def: Definition,
     famous_defs: Option<&FamousDefs<'_, '_>>,
     notable_traits: &[(Trait, Vec<(Option<Type>, Name)>)],
-    macro_arm: Option<u32>,
     config: &HoverConfig,
 ) -> Markup {
     let mod_path = definition_mod_path(db, &def);
@@ -414,13 +413,6 @@ pub(super) fn definition(
         Definition::Adt(Adt::Struct(struct_)) => {
             struct_.display_limited(db, config.max_struct_field_count).to_string()
         }
-        Definition::Macro(it) => {
-            let mut label = it.display(db).to_string();
-            if let Some(macro_arm) = macro_arm {
-                format_to!(label, " // matched arm #{}", macro_arm);
-            }
-            label
-        }
         _ => def.label(db),
     };
     let docs = def.docs(db, famous_defs);
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index ca013da709..3fef16df25 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -188,14 +188,7 @@ impl StaticIndex<'_> {
             } else {
                 let it = self.tokens.insert(TokenStaticData {
                     documentation: documentation_for_definition(&sema, def, &node),
-                    hover: Some(hover_for_definition(
-                        &sema,
-                        file_id,
-                        def,
-                        &node,
-                        None,
-                        &hover_config,
-                    )),
+                    hover: Some(hover_for_definition(&sema, file_id, def, &node, &hover_config)),
                     definition: def.try_to_nav(self.db).map(UpmappingResult::call_site).map(|it| {
                         FileRange { file_id: it.file_id, range: it.focus_or_full_range() }
                     }),
diff --git a/crates/mbe/src/benchmark.rs b/crates/mbe/src/benchmark.rs
index f4bbaef7af..1dca288017 100644
--- a/crates/mbe/src/benchmark.rs
+++ b/crates/mbe/src/benchmark.rs
@@ -48,7 +48,7 @@ fn benchmark_expand_macro_rules() {
             .map(|(id, tt)| {
                 let res = rules[&id].expand(&tt, |_| (), true, DUMMY, Edition::CURRENT);
                 assert!(res.err.is_none());
-                res.value.0.token_trees.len()
+                res.value.token_trees.len()
             })
             .sum()
     };
diff --git a/crates/mbe/src/expander.rs b/crates/mbe/src/expander.rs
index cfad8bcc0b..2d495da0db 100644
--- a/crates/mbe/src/expander.rs
+++ b/crates/mbe/src/expander.rs
@@ -9,7 +9,7 @@ use rustc_hash::FxHashMap;
 use span::{Edition, Span};
 use syntax::SmolStr;
 
-use crate::{parser::MetaVarKind, ExpandError, ExpandResult, MatchedArmIndex};
+use crate::{parser::MetaVarKind, ExpandError, ExpandResult};
 
 pub(crate) fn expand_rules(
     rules: &[crate::Rule],
@@ -18,9 +18,9 @@ pub(crate) fn expand_rules(
     new_meta_vars: bool,
     call_site: Span,
     def_site_edition: Edition,
-) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
-    let mut match_: Option<(matcher::Match, &crate::Rule, usize)> = None;
-    for (idx, rule) in rules.iter().enumerate() {
+) -> ExpandResult<tt::Subtree<Span>> {
+    let mut match_: Option<(matcher::Match, &crate::Rule)> = None;
+    for rule in rules {
         let new_match = matcher::match_(&rule.lhs, input, def_site_edition);
 
         if new_match.err.is_none() {
@@ -35,34 +35,31 @@ pub(crate) fn expand_rules(
                 call_site,
             );
             if transcribe_err.is_none() {
-                return ExpandResult::ok((value, Some(idx as u32)));
+                return ExpandResult::ok(value);
             }
         }
         // Use the rule if we matched more tokens, or bound variables count
-        if let Some((prev_match, _, _)) = &match_ {
+        if let Some((prev_match, _)) = &match_ {
             if (new_match.unmatched_tts, -(new_match.bound_count as i32))
                 < (prev_match.unmatched_tts, -(prev_match.bound_count as i32))
             {
-                match_ = Some((new_match, rule, idx));
+                match_ = Some((new_match, rule));
             }
         } else {
-            match_ = Some((new_match, rule, idx));
+            match_ = Some((new_match, rule));
         }
     }
-    if let Some((match_, rule, idx)) = match_ {
+    if let Some((match_, rule)) = match_ {
         // if we got here, there was no match without errors
         let ExpandResult { value, err: transcribe_err } =
             transcriber::transcribe(&rule.rhs, &match_.bindings, marker, new_meta_vars, call_site);
-        ExpandResult { value: (value, idx.try_into().ok()), err: match_.err.or(transcribe_err) }
+        ExpandResult { value, err: match_.err.or(transcribe_err) }
     } else {
         ExpandResult::new(
-            (
-                tt::Subtree {
-                    delimiter: tt::Delimiter::invisible_spanned(call_site),
-                    token_trees: Box::default(),
-                },
-                None,
-            ),
+            tt::Subtree {
+                delimiter: tt::Delimiter::invisible_spanned(call_site),
+                token_trees: Box::new([]),
+            },
             ExpandError::NoMatchingRule,
         )
     }
diff --git a/crates/mbe/src/lib.rs b/crates/mbe/src/lib.rs
index d5de56312a..5445f8790f 100644
--- a/crates/mbe/src/lib.rs
+++ b/crates/mbe/src/lib.rs
@@ -122,9 +122,6 @@ impl fmt::Display for CountError {
     }
 }
 
-/// Index of the matched macro arm on successful expansion.
-pub type MatchedArmIndex = Option<u32>;
-
 /// This struct contains AST for a single `macro_rules` definition. What might
 /// be very confusing is that AST has almost exactly the same shape as
 /// `tt::TokenTree`, but there's a crucial difference: in macro rules, `$ident`
@@ -254,7 +251,7 @@ impl DeclarativeMacro {
         new_meta_vars: bool,
         call_site: Span,
         def_site_edition: Edition,
-    ) -> ExpandResult<(tt::Subtree<Span>, MatchedArmIndex)> {
+    ) -> ExpandResult<tt::Subtree<Span>> {
         expander::expand_rules(&self.rules, tt, marker, new_meta_vars, call_site, def_site_edition)
     }
 }
@@ -333,10 +330,6 @@ impl<T, E> ValueResult<T, E> {
         Self { value: Default::default(), err: Some(err) }
     }
 
-    pub fn zip_val<U>(self, other: U) -> ValueResult<(T, U), E> {
-        ValueResult { value: (self.value, other), err: self.err }
-    }
-
     pub fn map<U>(self, f: impl FnOnce(T) -> U) -> ValueResult<U, E> {
         ValueResult { value: f(self.value), err: self.err }
     }
diff --git a/crates/proc-macro-srv-cli/build.rs b/crates/proc-macro-srv-cli/build.rs
deleted file mode 100644
index 07f914fece..0000000000
--- a/crates/proc-macro-srv-cli/build.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-//! This teaches cargo about our cfg(rust_analyzer)
-
-fn main() {
-    println!("cargo:rustc-check-cfg=cfg(rust_analyzer)");
-}
diff --git a/crates/proc-macro-srv/build.rs b/crates/proc-macro-srv/build.rs
index 874d1c6cd3..a8c732f315 100644
--- a/crates/proc-macro-srv/build.rs
+++ b/crates/proc-macro-srv/build.rs
@@ -4,8 +4,6 @@
 use std::{env, fs::File, io::Write, path::PathBuf, process::Command};
 
 fn main() {
-    println!("cargo:rustc-check-cfg=cfg(rust_analyzer)");
-
     let mut path = PathBuf::from(env::var_os("OUT_DIR").unwrap());
     path.push("rustc_version.rs");
     let mut f = File::create(&path).unwrap();
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 6a5f7b0262..83dd99b36b 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -33,55 +33,53 @@ impl CargoTargetSpec {
         kind: &RunnableKind,
         cfg: &Option<CfgExpr>,
     ) -> (Vec<String>, Vec<String>) {
-        let extra_test_binary_args = snap.config.runnables().extra_test_binary_args;
-
-        let mut cargo_args = Vec::new();
-        let mut executable_args = Vec::new();
+        let mut args = Vec::new();
+        let mut extra_args = Vec::new();
 
         match kind {
             RunnableKind::Test { test_id, attr } => {
-                cargo_args.push("test".to_owned());
-                executable_args.push(test_id.to_string());
+                args.push("test".to_owned());
+                extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    executable_args.push("--exact".to_owned());
+                    extra_args.push("--exact".to_owned());
                 }
-                executable_args.extend(extra_test_binary_args);
+                extra_args.push("--nocapture".to_owned());
                 if attr.ignore {
-                    executable_args.push("--ignored".to_owned());
+                    extra_args.push("--ignored".to_owned());
                 }
             }
             RunnableKind::TestMod { path } => {
-                cargo_args.push("test".to_owned());
-                executable_args.push(path.clone());
-                executable_args.extend(extra_test_binary_args);
+                args.push("test".to_owned());
+                extra_args.push(path.clone());
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::Bench { test_id } => {
-                cargo_args.push("bench".to_owned());
-                executable_args.push(test_id.to_string());
+                args.push("bench".to_owned());
+                extra_args.push(test_id.to_string());
                 if let TestId::Path(_) = test_id {
-                    executable_args.push("--exact".to_owned());
+                    extra_args.push("--exact".to_owned());
                 }
-                executable_args.extend(extra_test_binary_args);
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::DocTest { test_id } => {
-                cargo_args.push("test".to_owned());
-                cargo_args.push("--doc".to_owned());
-                executable_args.push(test_id.to_string());
-                executable_args.extend(extra_test_binary_args);
+                args.push("test".to_owned());
+                args.push("--doc".to_owned());
+                extra_args.push(test_id.to_string());
+                extra_args.push("--nocapture".to_owned());
             }
             RunnableKind::Bin => {
                 let subcommand = match spec {
                     Some(CargoTargetSpec { target_kind: TargetKind::Test, .. }) => "test",
                     _ => "run",
                 };
-                cargo_args.push(subcommand.to_owned());
+                args.push(subcommand.to_owned());
             }
         }
 
         let (allowed_features, target_required_features) = if let Some(mut spec) = spec {
             let allowed_features = mem::take(&mut spec.features);
             let required_features = mem::take(&mut spec.required_features);
-            spec.push_to(&mut cargo_args, kind);
+            spec.push_to(&mut args, kind);
             (allowed_features, required_features)
         } else {
             (Default::default(), Default::default())
@@ -91,10 +89,10 @@ impl CargoTargetSpec {
 
         match &cargo_config.features {
             CargoFeatures::All => {
-                cargo_args.push("--all-features".to_owned());
+                args.push("--all-features".to_owned());
                 for feature in target_required_features {
-                    cargo_args.push("--features".to_owned());
-                    cargo_args.push(feature);
+                    args.push("--features".to_owned());
+                    args.push(feature);
                 }
             }
             CargoFeatures::Selected { features, no_default_features } => {
@@ -110,16 +108,16 @@ impl CargoTargetSpec {
 
                 feats.dedup();
                 for feature in feats {
-                    cargo_args.push("--features".to_owned());
-                    cargo_args.push(feature);
+                    args.push("--features".to_owned());
+                    args.push(feature);
                 }
 
                 if *no_default_features {
-                    cargo_args.push("--no-default-features".to_owned());
+                    args.push("--no-default-features".to_owned());
                 }
             }
         }
-        (cargo_args, executable_args)
+        (args, extra_args)
     }
 
     pub(crate) fn for_file(
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e0ce81a75a..14a4185654 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -392,14 +392,6 @@ config_data! {
         /// Additional arguments to be passed to cargo for runnables such as
         /// tests or binaries. For example, it may be `--release`.
         runnables_extraArgs: Vec<String>   = vec![],
-        /// Additional arguments to be passed through Cargo to launched tests, benchmarks, or
-        /// doc-tests.
-        ///
-        /// Unless the launched target uses a
-        /// [custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field),
-        /// they will end up being interpreted as options to
-        /// [`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments).
-        runnables_extraTestBinaryArgs: Vec<String> = vec!["--show-output".to_owned()],
 
         /// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
         /// projects, or "discover" to try to automatically find it if the `rustc-dev` component
@@ -831,8 +823,6 @@ pub struct RunnablesConfig {
     pub override_cargo: Option<String>,
     /// Additional arguments for the `cargo`, e.g. `--release`.
     pub cargo_extra_args: Vec<String>,
-    /// Additional arguments for the binary being run, if it is a test or benchmark.
-    pub extra_test_binary_args: Vec<String>,
 }
 
 /// Configuration for workspace symbol search requests.
@@ -1759,7 +1749,6 @@ impl Config {
         RunnablesConfig {
             override_cargo: self.runnables_command().clone(),
             cargo_extra_args: self.runnables_extraArgs().clone(),
-            extra_test_binary_args: self.runnables_extraTestBinaryArgs().clone(),
         }
     }
 
diff --git a/crates/span/src/map.rs b/crates/span/src/map.rs
index 81fc56c961..6d8c9c30fb 100644
--- a/crates/span/src/map.rs
+++ b/crates/span/src/map.rs
@@ -15,9 +15,6 @@ use crate::{
 #[derive(Debug, PartialEq, Eq, Clone, Hash)]
 pub struct SpanMap<S> {
     spans: Vec<(TextSize, SpanData<S>)>,
-    /// Index of the matched macro arm on successful expansion for declarative macros.
-    // FIXME: Does it make sense to have this here?
-    pub matched_arm: Option<u32>,
 }
 
 impl<S> SpanMap<S>
@@ -26,7 +23,7 @@ where
 {
     /// Creates a new empty [`SpanMap`].
     pub fn empty() -> Self {
-        Self { spans: Vec::new(), matched_arm: None }
+        Self { spans: Vec::new() }
     }
 
     /// Finalizes the [`SpanMap`], shrinking its backing storage and validating that the offsets are
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs
index 45bb777d4d..4cfdec2b5c 100644
--- a/crates/vfs-notify/src/lib.rs
+++ b/crates/vfs-notify/src/lib.rs
@@ -9,10 +9,7 @@
 
 #![warn(rust_2018_idioms, unused_lifetimes)]
 
-use std::{
-    fs,
-    path::{Component, Path},
-};
+use std::fs;
 
 use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
 use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
@@ -209,11 +206,6 @@ impl NotifyActor {
                                 return true;
                             }
                             let path = entry.path();
-
-                            if path_is_parent_symlink(path) {
-                                return false;
-                            }
-
                             root == path
                                 || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
                         });
@@ -266,21 +258,3 @@ fn read(path: &AbsPath) -> Option<Vec<u8>> {
 fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
     res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
 }
-
-/// Is `path` a symlink to a parent directory?
-///
-/// Including this path is guaranteed to cause an infinite loop. This
-/// heuristic is not sufficient to catch all symlink cycles (it's
-/// possible to construct cycle using two or more symlinks), but it
-/// catches common cases.
-fn path_is_parent_symlink(path: &Path) -> bool {
-    let Ok(destination) = std::fs::read_link(path) else {
-        return false;
-    };
-
-    // If the symlink is of the form "../..", it's a parent symlink.
-    let is_relative_parent =
-        destination.components().all(|c| matches!(c, Component::CurDir | Component::ParentDir));
-
-    is_relative_parent || path.starts_with(destination)
-}
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index d509e58612..af4483a2cc 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -851,24 +851,6 @@ Command to be executed instead of 'cargo' for runnables.
 --
 Additional arguments to be passed to cargo for runnables such as
 tests or binaries. For example, it may be `--release`.
---
-[[rust-analyzer.runnables.extraTestBinaryArgs]]rust-analyzer.runnables.extraTestBinaryArgs::
-+
---
-Default:
-----
-[
-  "--show-output"
-]
-----
-Additional arguments to be passed through Cargo to launched tests, benchmarks, or
-doc-tests.
-
-Unless the launched target uses a
-[custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field),
-they will end up being interpreted as options to
-[`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments).
-
 --
 [[rust-analyzer.rustc.source]]rust-analyzer.rustc.source (default: `null`)::
 +
diff --git a/editors/code/package.json b/editors/code/package.json
index b1f23a3d9d..c387e72a0c 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1594,16 +1594,6 @@
                         "type": "string"
                     }
                 },
-                "rust-analyzer.runnables.extraTestBinaryArgs": {
-                    "markdownDescription": "Additional arguments to be passed through Cargo to launched tests, benchmarks, or\ndoc-tests.\n\nUnless the launched target uses a\n[custom test harness](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-harness-field),\nthey will end up being interpreted as options to\n[`rustc`’s built-in test harness (“libtest”)](https://doc.rust-lang.org/rustc/tests/index.html#cli-arguments).",
-                    "default": [
-                        "--show-output"
-                    ],
-                    "type": "array",
-                    "items": {
-                        "type": "string"
-                    }
-                },
                 "rust-analyzer.rustc.source": {
                     "markdownDescription": "Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private\nprojects, or \"discover\" to try to automatically find it if the `rustc-dev` component\nis installed.\n\nAny project which uses rust-analyzer with the rustcPrivate\ncrates must set `[package.metadata.rust-analyzer] rustc_private=true` to use it.\n\nThis option does not take effect until rust-analyzer is restarted.",
                     "default": null,


Test patch:
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index d0355bd028..937e78f8d7 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -390,6 +390,36 @@ fn main() {
     )
 }
 
+#[test]
+fn doctest_convert_from_to_tryfrom() {
+    check_doc_test(
+        "convert_from_to_tryfrom",
+        r#####"
+//- minicore: from
+impl $0From<usize> for Thing {
+    fn from(val: usize) -> Self {
+        Thing {
+            b: val.to_string(),
+            a: val
+        }
+    }
+}
+"#####,
+        r#####"
+impl TryFrom<usize> for Thing {
+    type Error = ${0:()};
+
+    fn try_from(val: usize) -> Result<Self, Self::Error> {
+        Ok(Thing {
+            b: val.to_string(),
+            a: val
+        })
+    }
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_convert_if_to_bool_then() {
     check_doc_test(
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index 6bbc8b380d..67f10f0374 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -1560,21 +1560,21 @@ fn y() {
 fn test_hover_macro_invocation() {
     check(
         r#"
-macro_rules! foo { (a) => {}; () => {} }
+macro_rules! foo { () => {} }
 
 fn f() { fo$0o!(); }
 "#,
         expect![[r#"
-            *foo*
+                *foo*
 
-            ```rust
-            test
-            ```
+                ```rust
+                test
+                ```
 
-            ```rust
-            macro_rules! foo // matched arm #1
-            ```
-        "#]],
+                ```rust
+                macro_rules! foo
+                ```
+            "#]],
     )
 }
 
@@ -1590,22 +1590,22 @@ macro foo() {}
 fn f() { fo$0o!(); }
 "#,
         expect![[r#"
-            *foo*
+                *foo*
 
-            ```rust
-            test
-            ```
+                ```rust
+                test
+                ```
 
-            ```rust
-            macro foo // matched arm #0
-            ```
+                ```rust
+                macro foo
+                ```
 
-            ---
+                ---
 
-            foo bar
+                foo bar
 
-            foo bar baz
-        "#]],
+                foo bar baz
+            "#]],
     )
 }
 
diff --git a/crates/proc-macro-srv/proc-macro-test/build.rs b/crates/proc-macro-srv/proc-macro-test/build.rs
index 6a0ae362d8..c76c201d69 100644
--- a/crates/proc-macro-srv/proc-macro-test/build.rs
+++ b/crates/proc-macro-srv/proc-macro-test/build.rs
@@ -53,7 +53,7 @@ fn main() {
     println!("Creating {}", src_dir.display());
     std::fs::create_dir_all(src_dir).unwrap();
 
-    for item_els in [&["Cargo.toml"][..], &["build.rs"][..], &["src", "lib.rs"]] {
+    for item_els in [&["Cargo.toml"][..], &["src", "lib.rs"]] {
         let mut src = imp_dir.clone();
         let mut dst = staging_dir.clone();
         for el in item_els {
diff --git a/crates/proc-macro-srv/proc-macro-test/imp/build.rs b/crates/proc-macro-srv/proc-macro-test/imp/build.rs
deleted file mode 100644
index 07f914fece..0000000000
--- a/crates/proc-macro-srv/proc-macro-test/imp/build.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-//! This teaches cargo about our cfg(rust_analyzer)
-
-fn main() {
-    println!("cargo:rustc-check-cfg=cfg(rust_analyzer)");
-}
diff --git a/crates/rust-analyzer/tests/slow-tests/main.rs b/crates/rust-analyzer/tests/slow-tests/main.rs
index 786a80c4a6..8c982b4687 100644
--- a/crates/rust-analyzer/tests/slow-tests/main.rs
+++ b/crates/rust-analyzer/tests/slow-tests/main.rs
@@ -157,7 +157,7 @@ fn main() {}
           {
             "args": {
               "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
-              "executableArgs": ["test_eggs", "--exact", "--show-output"],
+              "executableArgs": ["test_eggs", "--exact", "--nocapture"],
               "cargoExtraArgs": [],
               "overrideCargo": null,
               "workspaceRoot": server.path().join("foo")
@@ -190,7 +190,7 @@ fn main() {}
               "cargoExtraArgs": [],
               "executableArgs": [
                 "",
-                "--show-output"
+                "--nocapture"
               ]
             },
             "kind": "cargo",