Repository: rust-lang/rust-analyzer
Pull request #13671 (2022-11-24 23:05:40)

Improve goto declaration
Closes https://github.com/rust-lang/rust-analyzer/issues/13599

- goto decl now goes to assoc items of trait declarations over the items of trait implementations
- goto decl now goes to the field declaration (opposed to goto def which shows both the field decl and binding created/local being used)
- also adds back the goto definition fallback that seems to have been dropped at some point.


Patch:
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index f13088cd90..8f93dad06e 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -360,20 +360,13 @@ impl FlycheckActor {
     }
 }
 
-struct JodGroupChild(GroupChild);
-
-impl Drop for JodGroupChild {
-    fn drop(&mut self) {
-        _ = self.0.kill();
-        _ = self.0.wait();
-    }
-}
+struct JodChild(GroupChild);
 
 /// A handle to a cargo process used for fly-checking.
 struct CargoHandle {
     /// The handle to the actual cargo process. As we cannot cancel directly from with
     /// a read syscall dropping and therefore terminating the process is our best option.
-    child: JodGroupChild,
+    child: JodChild,
     thread: jod_thread::JoinHandle<io::Result<(bool, String)>>,
     receiver: Receiver<CargoMessage>,
 }
@@ -381,7 +374,7 @@ struct CargoHandle {
 impl CargoHandle {
     fn spawn(mut command: Command) -> std::io::Result<CargoHandle> {
         command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
-        let mut child = command.group_spawn().map(JodGroupChild)?;
+        let mut child = command.group_spawn().map(JodChild)?;
 
         let stdout = child.0.inner().stdout.take().unwrap();
         let stderr = child.0.inner().stderr.take().unwrap();
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index 985954d44e..7b19518e25 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -379,10 +379,15 @@ fn compile_error_expand(
     tt: &tt::Subtree,
 ) -> ExpandResult<ExpandedEager> {
     let err = match &*tt.token_trees {
-        [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) {
-            Some(unquoted) => ExpandError::Other(unquoted.into()),
-            None => ExpandError::Other("`compile_error!` argument must be a string".into()),
-        },
+        [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => {
+            let text = it.text.as_str();
+            if text.starts_with('"') && text.ends_with('"') {
+                // FIXME: does not handle raw strings
+                ExpandError::Other(text[1..text.len() - 1].into())
+            } else {
+                ExpandError::Other("`compile_error!` argument must be a string".into())
+            }
+        }
         _ => ExpandError::Other("`compile_error!` argument must be a string".into()),
     };
 
diff --git a/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
index 161bcc5c8d..2b3793659c 100644
--- a/crates/ide-assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ide-assists/src/handlers/add_missing_impl_members.rs
@@ -107,14 +107,6 @@ fn add_missing_impl_members_inner(
 ) -> Option<()> {
     let _p = profile::span("add_missing_impl_members_inner");
     let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
-
-    if ctx.token_at_offset().all(|t| {
-        t.parent_ancestors()
-            .any(|s| ast::BlockExpr::can_cast(s.kind()) || ast::ParamList::can_cast(s.kind()))
-    }) {
-        return None;
-    }
-
     let target_scope = ctx.sema.scope(impl_def.syntax())?;
     let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
 
@@ -1351,95 +1343,4 @@ impl PartialEq for SomeStruct {
 "#,
         );
     }
-
-    #[test]
-    fn test_ignore_function_body() {
-        check_assist_not_applicable(
-            add_missing_default_members,
-            r#"
-trait Trait {
-    type X;
-    fn foo(&self);
-    fn bar(&self) {}
-}
-
-impl Trait for () {
-    type X = u8;
-    fn foo(&self) {$0
-        let x = 5;
-    }
-}"#,
-        )
-    }
-
-    #[test]
-    fn test_ignore_param_list() {
-        check_assist_not_applicable(
-            add_missing_impl_members,
-            r#"
-trait Trait {
-    type X;
-    fn foo(&self);
-    fn bar(&self);
-}
-
-impl Trait for () {
-    type X = u8;
-    fn foo(&self$0) {
-        let x = 5;
-    }
-}"#,
-        )
-    }
-
-    #[test]
-    fn test_ignore_scope_inside_function() {
-        check_assist_not_applicable(
-            add_missing_impl_members,
-            r#"
-trait Trait {
-    type X;
-    fn foo(&self);
-    fn bar(&self);
-}
-
-impl Trait for () {
-    type X = u8;
-    fn foo(&self) {
-        let x = async {$0 5 };
-    }
-}"#,
-        )
-    }
-
-    #[test]
-    fn test_apply_outside_function() {
-        check_assist(
-            add_missing_default_members,
-            r#"
-trait Trait {
-    type X;
-    fn foo(&self);
-    fn bar(&self) {}
-}
-
-impl Trait for () {
-    type X = u8;
-    fn foo(&self)$0 {}
-}"#,
-            r#"
-trait Trait {
-    type X;
-    fn foo(&self);
-    fn bar(&self) {}
-}
-
-impl Trait for () {
-    type X = u8;
-    fn foo(&self) {}
-
-    $0fn bar(&self) {}
-}"#,
-        )
-    }
 }
diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs
index 690c97e26d..9af26c04eb 100644
--- a/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_impl.rs
@@ -1,17 +1,14 @@
 use syntax::ast::{self, AstNode, HasName};
 
-use crate::{
-    utils::{generate_impl_text, generate_trait_impl_text},
-    AssistContext, AssistId, AssistKind, Assists,
-};
+use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists};
 
 // Assist: generate_impl
 //
 // Adds a new inherent impl for a type.
 //
 // ```
-// struct Ctx$0<T: Clone> {
-//     data: T,
+// struct Ctx<T: Clone> {
+//     data: T,$0
 // }
 // ```
 // ->
@@ -29,10 +26,6 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
     let name = nominal.name()?;
     let target = nominal.syntax().text_range();
 
-    if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() {
-        return None;
-    }
-
     acc.add(
         AssistId("generate_impl", AssistKind::Generate),
         format!("Generate impl for `{name}`"),
@@ -53,393 +46,145 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
     )
 }
 
-// Assist: generate_trait_impl
-//
-// Adds a new trait impl for a type.
-//
-// ```
-// struct $0Ctx<T: Clone> {
-//     data: T,
-// }
-// ```
-// ->
-// ```
-// struct Ctx<T: Clone> {
-//     data: T,
-// }
-//
-// impl<T: Clone> $0 for Ctx<T> {
-//
-// }
-// ```
-pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
-    let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
-    let name = nominal.name()?;
-    let target = nominal.syntax().text_range();
-
-    if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() {
-        return None;
-    }
-
-    acc.add(
-        AssistId("generate_trait_impl", AssistKind::Generate),
-        format!("Generate trait impl for `{name}`"),
-        target,
-        |edit| {
-            let start_offset = nominal.syntax().text_range().end();
-            match ctx.config.snippet_cap {
-                Some(cap) => {
-                    let snippet = generate_trait_impl_text(&nominal, "$0", "");
-                    edit.insert_snippet(cap, start_offset, snippet);
-                }
-                None => {
-                    let text = generate_trait_impl_text(&nominal, "", "");
-                    edit.insert(start_offset, text);
-                }
-            }
-        },
-    )
-}
-
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_target};
 
     use super::*;
 
+    // FIXME: break up into separate test fns
     #[test]
     fn test_add_impl() {
         check_assist(
             generate_impl,
-            r#"
-                struct Foo$0 {}
-            "#,
-            r#"
-                struct Foo {}
-
-                impl Foo {
-                    $0
-                }
-            "#,
+            "struct Foo {$0}\n",
+            "struct Foo {}\n\nimpl Foo {\n    $0\n}\n",
         );
-    }
-
-    #[test]
-    fn test_add_impl_with_generics() {
         check_assist(
             generate_impl,
-            r#"
-                struct Foo$0<T: Clone> {}
-            "#,
-            r#"
-                struct Foo<T: Clone> {}
-
-                impl<T: Clone> Foo<T> {
-                    $0
-                }
-            "#,
+            "struct Foo<T: Clone> {$0}",
+            "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n    $0\n}",
         );
-    }
-
-    #[test]
-    fn test_add_impl_with_generics_and_lifetime_parameters() {
         check_assist(
             generate_impl,
-            r#"
-                struct Foo<'a, T: Foo<'a>>$0 {}
-            "#,
-            r#"
-                struct Foo<'a, T: Foo<'a>> {}
-
-                impl<'a, T: Foo<'a>> Foo<'a, T> {
-                    $0
-                }
-            "#,
+            "struct Foo<'a, T: Foo<'a>> {$0}",
+            "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n    $0\n}",
         );
-    }
-
-    #[test]
-    fn test_add_impl_with_attributes() {
         check_assist(
             generate_impl,
             r#"
-                #[cfg(feature = "foo")]
-                struct Foo<'a, T: Foo$0<'a>> {}
-            "#,
+            struct MyOwnArray<T, const S: usize> {}$0"#,
             r#"
-                #[cfg(feature = "foo")]
-                struct Foo<'a, T: Foo<'a>> {}
+            struct MyOwnArray<T, const S: usize> {}
 
-                #[cfg(feature = "foo")]
-                impl<'a, T: Foo<'a>> Foo<'a, T> {
-                    $0
-                }
-            "#,
+            impl<T, const S: usize> MyOwnArray<T, S> {
+                $0
+            }"#,
         );
-    }
-
-    #[test]
-    fn test_add_impl_with_default_generic() {
         check_assist(
             generate_impl,
             r#"
-                struct Defaulted$0<T = i32> {}
-            "#,
+            #[cfg(feature = "foo")]
+            struct Foo<'a, T: Foo<'a>> {$0}"#,
             r#"
-                struct Defaulted<T = i32> {}
+            #[cfg(feature = "foo")]
+            struct Foo<'a, T: Foo<'a>> {}
 
-                impl<T> Defaulted<T> {
-                    $0
-                }
-            "#,
+            #[cfg(feature = "foo")]
+            impl<'a, T: Foo<'a>> Foo<'a, T> {
+                $0
+            }"#,
         );
-    }
 
-    #[test]
-    fn test_add_impl_with_constrained_default_generic() {
         check_assist(
             generate_impl,
             r#"
-                struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
-            "#,
+            #[cfg(not(feature = "foo"))]
+            struct Foo<'a, T: Foo<'a>> {$0}"#,
             r#"
-                struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+            #[cfg(not(feature = "foo"))]
+            struct Foo<'a, T: Foo<'a>> {}
 
-                impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
-                    $0
-                }
-            "#,
+            #[cfg(not(feature = "foo"))]
+            impl<'a, T: Foo<'a>> Foo<'a, T> {
+                $0
+            }"#,
         );
-    }
 
-    #[test]
-    fn test_add_impl_with_const_defaulted_generic() {
         check_assist(
             generate_impl,
             r#"
-                struct Defaulted$0<const N: i32 = 0> {}
-            "#,
+            struct Defaulted<T = i32> {}$0"#,
             r#"
-                struct Defaulted<const N: i32 = 0> {}
+            struct Defaulted<T = i32> {}
 
-                impl<const N: i32> Defaulted<N> {
-                    $0
-                }
-            "#,
+            impl<T> Defaulted<T> {
+                $0
+            }"#,
         );
-    }
 
-    #[test]
-    fn test_add_impl_with_trait_constraint() {
         check_assist(
             generate_impl,
             r#"
-                pub trait Trait {}
-                struct Struct$0<T>
-                where
-                    T: Trait,
-                {
-                    inner: T,
-                }
-            "#,
+            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#,
             r#"
-                pub trait Trait {}
-                struct Struct<T>
-                where
-                    T: Trait,
-                {
-                    inner: T,
-                }
+            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
 
-                impl<T> Struct<T>
-                where
-                    T: Trait,
-                {
-                    $0
-                }
-            "#,
+            impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
+                $0
+            }"#,
         );
-    }
 
-    #[test]
-    fn add_impl_target() {
-        check_assist_target(
-            generate_impl,
-            r#"
-                struct SomeThingIrrelevant;
-                /// Has a lifetime parameter
-                struct Foo$0<'a, T: Foo<'a>> {}
-                struct EvenMoreIrrelevant;
-            "#,
-            "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}",
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl() {
         check_assist(
-            generate_trait_impl,
-            r#"
-                struct Foo$0 {}
-            "#,
-            r#"
-                struct Foo {}
-
-                impl $0 for Foo {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_generics() {
-        check_assist(
-            generate_trait_impl,
-            r#"
-                struct Foo$0<T: Clone> {}
-            "#,
-            r#"
-                struct Foo<T: Clone> {}
-
-                impl<T: Clone> $0 for Foo<T> {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_generics_and_lifetime_parameters() {
-        check_assist(
-            generate_trait_impl,
-            r#"
-                struct Foo<'a, T: Foo<'a>>$0 {}
-            "#,
-            r#"
-                struct Foo<'a, T: Foo<'a>> {}
-
-                impl<'a, T: Foo<'a>> $0 for Foo<'a, T> {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_attributes() {
-        check_assist(
-            generate_trait_impl,
-            r#"
-                #[cfg(feature = "foo")]
-                struct Foo<'a, T: Foo$0<'a>> {}
-            "#,
-            r#"
-                #[cfg(feature = "foo")]
-                struct Foo<'a, T: Foo<'a>> {}
-
-                #[cfg(feature = "foo")]
-                impl<'a, T: Foo<'a>> $0 for Foo<'a, T> {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_default_generic() {
-        check_assist(
-            generate_trait_impl,
-            r#"
-                struct Defaulted$0<T = i32> {}
-            "#,
-            r#"
-                struct Defaulted<T = i32> {}
-
-                impl<T> $0 for Defaulted<T> {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_constrained_default_generic() {
-        check_assist(
-            generate_trait_impl,
-            r#"
-                struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
-            "#,
-            r#"
-                struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
-
-                impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> $0 for Defaulted<'a, 'b, T, S> {
-
-                }
-            "#,
-        );
-    }
-
-    #[test]
-    fn test_add_trait_impl_with_const_defaulted_generic() {
-        check_assist(
-            generate_trait_impl,
+            generate_impl,
             r#"
-                struct Defaulted$0<const N: i32 = 0> {}
-            "#,
+            struct Defaulted<const N: i32 = 0> {}$0"#,
             r#"
-                struct Defaulted<const N: i32 = 0> {}
+            struct Defaulted<const N: i32 = 0> {}
 
-                impl<const N: i32> $0 for Defaulted<N> {
-
-                }
-            "#,
+            impl<const N: i32> Defaulted<N> {
+                $0
+            }"#,
         );
-    }
 
-    #[test]
-    fn test_add_trait_impl_with_trait_constraint() {
         check_assist(
-            generate_trait_impl,
-            r#"
-                pub trait Trait {}
-                struct Struct$0<T>
-                where
-                    T: Trait,
-                {
-                    inner: T,
-                }
-            "#,
-            r#"
-                pub trait Trait {}
-                struct Struct<T>
-                where
-                    T: Trait,
-                {
-                    inner: T,
-                }
-
-                impl<T> $0 for Struct<T>
-                where
-                    T: Trait,
-                {
+            generate_impl,
+            r#"pub trait Trait {}
+struct Struct<T>$0
+where
+    T: Trait,
+{
+    inner: T,
+}"#,
+            r#"pub trait Trait {}
+struct Struct<T>
+where
+    T: Trait,
+{
+    inner: T,
+}
 
-                }
-            "#,
+impl<T> Struct<T>
+where
+    T: Trait,
+{
+    $0
+}"#,
         );
     }
 
     #[test]
-    fn add_trait_impl_target() {
+    fn add_impl_target() {
         check_assist_target(
-            generate_trait_impl,
-            r#"
-                struct SomeThingIrrelevant;
-                /// Has a lifetime parameter
-                struct Foo$0<'a, T: Foo<'a>> {}
-                struct EvenMoreIrrelevant;
-            "#,
-            "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}",
+            generate_impl,
+            "
+struct SomeThingIrrelevant;
+/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {$0}
+struct EvenMoreIrrelevant;
+",
+            "/// Has a lifetime parameter
+struct Foo<'a, T: Foo<'a>> {}",
         );
     }
 }
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index b12f99cc53..387cc63142 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -247,7 +247,6 @@ mod handlers {
             generate_from_impl_for_enum::generate_from_impl_for_enum,
             generate_function::generate_function,
             generate_impl::generate_impl,
-            generate_impl::generate_trait_impl,
             generate_is_empty_from_len::generate_is_empty_from_len,
             generate_new::generate_new,
             inline_call::inline_call,
diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs
index 926292c9b3..c7130a2a4b 100644
--- a/crates/ide/src/goto_declaration.rs
+++ b/crates/ide/src/goto_declaration.rs
@@ -1,18 +1,22 @@
-use hir::Semantics;
+use hir::{AsAssocItem, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
 };
 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T};
 
-use crate::{FilePosition, NavigationTarget, RangeInfo};
+use crate::{
+    goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget,
+    RangeInfo,
+};
 
 // Feature: Go to Declaration
 //
 // Navigates to the declaration of an identifier.
 //
-// This is currently the same as `Go to Definition` with the exception of outline modules where it
-// will navigate to the `mod name;` item declaration.
+// 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
 pub(crate) fn goto_declaration(
     db: &RootDatabase,
     position: FilePosition,
@@ -32,25 +36,37 @@ pub(crate) fn goto_declaration(
                 match parent {
                     ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? {
                         NameRefClass::Definition(it) => Some(it),
-                        _ => None
+                        NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
                     },
                     ast::Name(name) => match NameClass::classify(&sema, &name)? {
-                        NameClass::Definition(it) => Some(it),
-                        _ => None
+                        NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
+                        NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db),
                     },
                     _ => None
                 }
             };
-            match def? {
+            let assoc = match def? {
                 Definition::Module(module) => {
-                    Some(NavigationTarget::from_module_to_decl(db, module))
+                    return Some(NavigationTarget::from_module_to_decl(db, module))
                 }
+                Definition::Const(c) => c.as_assoc_item(db),
+                Definition::TypeAlias(ta) => ta.as_assoc_item(db),
+                Definition::Function(f) => f.as_assoc_item(db),
                 _ => None,
-            }
+            }?;
+
+            let trait_ = assoc.containing_trait_impl(db)?;
+            let name = Some(assoc.name(db)?);
+            let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?;
+            item.try_to_nav(db)
         })
         .collect();
 
-    Some(RangeInfo::new(range, info))
+    if info.is_empty() {
+        goto_definition(db, position)
+    } else {
+        Some(RangeInfo::new(range, info))
+    }
 }
 
 #[cfg(test)]
@@ -109,4 +125,89 @@ mod foo {
 "#,
         )
     }
+
+    #[test]
+    fn goto_decl_goto_def_fallback() {
+        check(
+            r#"
+struct Foo;
+    // ^^^
+impl Foo$0 {}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_decl_assoc_item_no_impl_item() {
+        check(
+            r#"
+trait Trait {
+    const C: () = ();
+       // ^
+}
+impl Trait for () {}
+
+fn main() {
+    <()>::C$0;
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_decl_assoc_item() {
+        check(
+            r#"
+trait Trait {
+    const C: () = ();
+       // ^
+}
+impl Trait for () {
+    const C: () = ();
+}
+
+fn main() {
+    <()>::C$0;
+}
+"#,
+        );
+        check(
+            r#"
+trait Trait {
+    const C: () = ();
+       // ^
+}
+impl Trait for () {
+    const C$0: () = ();
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_decl_field_pat_shorthand() {
+        check(
+            r#"
+struct Foo { field: u32 }
+           //^^^^^
+fn main() {
+    let Foo { field$0 };
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_decl_constructor_shorthand() {
+        check(
+            r#"
+struct Foo { field: u32 }
+           //^^^^^
+fn main() {
+    let field = 0;
+    Foo { field$0 };
+}
+"#,
+        );
+    }
 }
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 9cbfed4763..838fb18c3d 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -127,7 +127,6 @@ pub(crate) fn hover(
             original_token.parent().and_then(ast::TokenTree::cast),
             Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
         );
-
     // prefer descending the same token kind in attribute expansions, in normal macros text
     // equivalency is more important
     let descended = if in_attr {
@@ -136,67 +135,54 @@ pub(crate) fn hover(
         sema.descend_into_macros_with_same_text(original_token.clone())
     };
 
-    // try lint hover
+    // FIXME: Definition should include known lints and the like instead of having this special case here
+    let hovered_lint = descended.iter().find_map(|token| {
+        let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+        render::try_for_lint(&attr, token)
+    });
+    if let Some(res) = hovered_lint {
+        return Some(RangeInfo::new(original_token.text_range(), res));
+    }
+
     let result = descended
         .iter()
-        .find_map(|token| {
-            // FIXME: Definition should include known lints and the like instead of having this special case here
-            let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
-            render::try_for_lint(&attr, token)
-        })
-        // try item definitions
-        .or_else(|| {
-            descended
-                .iter()
-                .filter_map(|token| {
-                    let node = token.parent()?;
-                    let class = IdentClass::classify_token(sema, token)?;
-                    if let IdentClass::Operator(OperatorClass::Await(_)) = class {
-                        // It's better for us to fall back to the keyword hover here,
-                        // rendering poll is very confusing
-                        return None;
-                    }
-                    Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
-                })
-                .flatten()
-                .unique_by(|&(def, _)| def)
-                .filter_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{}", acc.markup, markup));
-                    acc
-                })
+        .filter_map(|token| {
+            let node = token.parent()?;
+            let class = IdentClass::classify_token(sema, token)?;
+            if let IdentClass::Operator(OperatorClass::Await(_)) = class {
+                // It's better for us to fall back to the keyword hover here,
+                // rendering poll is very confusing
+                return None;
+            }
+            Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
         })
-        // try keywords
-        .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
-        // try rest item hover
-        .or_else(|| {
-            descended.iter().find_map(|token| {
-                if token.kind() != DOT2 {
-                    return None;
-                }
-
-                let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
-                let record_pat_field_list =
-                    rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
-
-                let record_pat =
-                    record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
-
-                Some(render::struct_rest_pat(sema, config, &record_pat))
-            })
+        .flatten()
+        .unique_by(|&(def, _)| def)
+        .filter_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{}", acc.markup, markup));
+            acc
         });
 
-    result
-        .map(|mut res: HoverResult| {
-            res.actions = dedupe_or_merge_hover_actions(res.actions);
-            RangeInfo::new(original_token.text_range(), res)
-        })
-        // fallback to type hover if there aren't any other suggestions
-        // this finds its own range instead of using the closest token's range
-        .or_else(|| {
-            descended.iter().find_map(|token| hover_type_fallback(sema, config, token, &token))
-        })
+    if result.is_none() {
+        // fallbacks, show keywords or types
+
+        let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
+        if let Some(res) = res {
+            return Some(RangeInfo::new(original_token.text_range(), res));
+        }
+        let res = descended
+            .iter()
+            .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
+        if let Some(_) = res {
+            return res;
+        }
+    }
+    result.map(|mut res: HoverResult| {
+        res.actions = dedupe_or_merge_hover_actions(res.actions);
+        RangeInfo::new(original_token.text_range(), res)
+    })
 }
 
 pub(crate) fn hover_for_definition(
@@ -283,7 +269,6 @@ fn hover_type_fallback(
     };
 
     let res = render::type_info(sema, config, &expr_or_pat)?;
-
     let range = sema
         .original_range_opt(&node)
         .map(|frange| frange.range)
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index fb00a40f96..d109c07691 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -14,9 +14,7 @@ use ide_db::{
 use itertools::Itertools;
 use stdx::format_to;
 use syntax::{
-    algo,
-    ast::{self, RecordPat},
-    match_ast, AstNode, Direction,
+    algo, ast, match_ast, AstNode, Direction,
     SyntaxKind::{LET_EXPR, LET_STMT},
     SyntaxToken, T,
 };
@@ -252,50 +250,6 @@ pub(super) fn keyword(
     Some(HoverResult { markup, actions })
 }
 
-/// Returns missing types in a record pattern.
-/// Only makes sense when there's a rest pattern in the record pattern.
-/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
-pub(super) fn struct_rest_pat(
-    sema: &Semantics<'_, RootDatabase>,
-    config: &HoverConfig,
-    pattern: &RecordPat,
-) -> HoverResult {
-    let missing_fields = sema.record_pattern_missing_fields(pattern);
-
-    // if there are no missing fields, the end result is a hover that shows ".."
-    // should be left in to indicate that there are no more fields in the pattern
-    // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
-
-    let mut res = HoverResult::default();
-    let mut targets: Vec<hir::ModuleDef> = Vec::new();
-    let mut push_new_def = |item: hir::ModuleDef| {
-        if !targets.contains(&item) {
-            targets.push(item);
-        }
-    };
-    for (_, t) in &missing_fields {
-        walk_and_push_ty(sema.db, &t, &mut push_new_def);
-    }
-
-    res.markup = {
-        let mut s = String::from(".., ");
-        for (f, _) in &missing_fields {
-            s += f.display(sema.db).to_string().as_ref();
-            s += ", ";
-        }
-        // get rid of trailing comma
-        s.truncate(s.len() - 2);
-
-        if config.markdown() {
-            Markup::fenced_block(&s)
-        } else {
-            s.into()
-        }
-    };
-    res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
-    res
-}
-
 pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
     let (path, tt) = attr.as_simple_call()?;
     if !tt.syntax().text_range().contains(token.text_range().start()) {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 6c0d712a4f..6b2f22faa7 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -182,7 +182,7 @@ config_data! {
         /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`.
         ///
         /// Aliased as `"checkOnSave.targets"`.
-        checkOnSave_target | checkOnSave_targets: Option<CheckOnSaveTargets>           = "null",
+        checkOnSave_target | checkOnSave_targets: CheckOnSaveTargets           = "[]",
 
         /// Toggles the additional completions that automatically add imports when completed.
         /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
@@ -1153,15 +1153,10 @@ impl Config {
             }
             Some(_) | None => FlycheckConfig::CargoCommand {
                 command: self.data.checkOnSave_command.clone(),
-                target_triples: self
-                    .data
-                    .checkOnSave_target
-                    .clone()
-                    .and_then(|targets| match &targets.0[..] {
-                        [] => None,
-                        targets => Some(targets.into()),
-                    })
-                    .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()),
+                target_triples: match &self.data.checkOnSave_target.0[..] {
+                    [] => self.data.cargo_target.clone().into_iter().collect(),
+                    targets => targets.into(),
+                },
                 all_targets: self.data.checkOnSave_allTargets,
                 no_default_features: self
                     .data
@@ -2131,11 +2126,8 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
                 "The command will be executed in the project root."
             ],
         },
-        "Option<CheckOnSaveTargets>" => set! {
+        "CheckOnSaveTargets" => set! {
             "anyOf": [
-                {
-                    "type": "null"
-                },
                 {
                     "type": "string",
                 },
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index db41c7bf10..57f950034c 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -190,7 +190,7 @@ cargo check --workspace --message-format=json --all-targets
 ```
 .
 --
-[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `null`)::
+[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `[]`)::
 +
 --
 Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.
diff --git a/editors/code/package.json b/editors/code/package.json
index ad5f97bb19..eb4f258120 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -640,11 +640,8 @@
                 },
                 "rust-analyzer.checkOnSave.target": {
                     "markdownDescription": "Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.\n\nCan be a single target, e.g. `\"x86_64-unknown-linux-gnu\"` or a list of targets, e.g.\n`[\"aarch64-apple-darwin\", \"x86_64-apple-darwin\"]`.\n\nAliased as `\"checkOnSave.targets\"`.",
-                    "default": null,
+                    "default": [],
                     "anyOf": [
-                        {
-                            "type": "null"
-                        },
                         {
                             "type": "string"
                         },


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
index bb45266725..c04cd16519 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs
@@ -163,8 +163,7 @@ macro_rules! compile_error {
 }
 
 // This expands to nothing (since it's in item position), but emits an error.
-compile_error!("error, with an escaped quote: \"");
-compile_error!(r"this is a raw string");
+compile_error!("error!");
 "#,
         expect![[r##"
 #[rustc_builtin_macro]
@@ -173,8 +172,7 @@ macro_rules! compile_error {
     ($msg:expr,) => ({ /* compiler built-in */ })
 }
 
-/* error: error, with an escaped quote: " */
-/* error: this is a raw string */
+/* error: error! */
 "##]],
     );
 }
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index d797f07767..c09317572a 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1249,8 +1249,8 @@ fn doctest_generate_impl() {
     check_doc_test(
         "generate_impl",
         r#####"
-struct Ctx$0<T: Clone> {
-    data: T,
+struct Ctx<T: Clone> {
+    data: T,$0
 }
 "#####,
         r#####"
@@ -1341,27 +1341,6 @@ impl Person {
     )
 }
 
-#[test]
-fn doctest_generate_trait_impl() {
-    check_doc_test(
-        "generate_trait_impl",
-        r#####"
-struct $0Ctx<T: Clone> {
-    data: T,
-}
-"#####,
-        r#####"
-struct Ctx<T: Clone> {
-    data: T,
-}
-
-impl<T: Clone> $0 for Ctx<T> {
-
-}
-"#####,
-    )
-}
-
 #[test]
 fn doctest_inline_call() {
     check_doc_test(
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index f8be4cfb04..eb997e6fef 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -5307,38 +5307,3 @@ fn main() { $0V; }
         "#]],
     );
 }
-
-#[test]
-fn hover_rest_pat() {
-    check(
-        r#"
-struct Struct {a: u32, b: u32, c: u8, d: u16};
-
-fn main() {
-    let Struct {a, c, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
-}
-"#,
-        expect![[r#"
-            *..*
-            ```rust
-            .., b: u32, d: u16
-            ```
-        "#]],
-    );
-
-    check(
-        r#"
-struct Struct {a: u32, b: u32, c: u8, d: u16};
-
-fn main() {
-    let Struct {a, b, c, d, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
-}
-"#,
-        expect![[r#"
-            *..*
-            ```rust
-            ..
-            ```
-        "#]],
-    );
-}