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

Issue:
Go to implementations should work on trait functions.
Feature request: "Go to Implementations" should work on trait functions.

E.g.:

```
trait Tr {
    fn f();
}

struct S;

impl Tr for S {
    fn f() {
        println!("Hello, world!");
    }
}
```

"Go to Implementations" on the trait's `fn f();` should go to the implementation in `S`. 

Pull request:
feat: go to implementation on trait functions
Fix #8537.

GIF:
![output](https://user-images.githubusercontent.com/6652840/119501981-45a45c00-bd1e-11eb-8336-9145f2888643.gif)

Patch:
diff --git a/crates/hir_ty/src/infer/expr.rs b/crates/hir_ty/src/infer/expr.rs
index 79a7321060..eab8fac910 100644
--- a/crates/hir_ty/src/infer/expr.rs
+++ b/crates/hir_ty/src/infer/expr.rs
@@ -805,7 +805,7 @@ impl<'a> InferenceContext<'a> {
                     None => self.table.new_float_var(),
                 },
             },
-            Expr::MacroStmts { tail } => self.infer_expr_inner(*tail, expected),
+            Expr::MacroStmts { tail } => self.infer_expr(*tail, expected),
         };
         // use a new type variable if we got unknown here
         let ty = self.insert_type_vars_shallow(ty);
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 05130a2376..5a8d3c3f98 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
-use hir::{Impl, Semantics};
+use hir::{AsAssocItem, Impl, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -36,6 +36,7 @@ pub(crate) fn goto_implementation(
         }
         ast::NameLike::Lifetime(_) => None,
     }?;
+
     let def = match def {
         Definition::ModuleDef(def) => def,
         _ => return None,
@@ -48,6 +49,12 @@ pub(crate) fn goto_implementation(
             let module = sema.to_module_def(position.file_id)?;
             impls_for_ty(&sema, builtin.ty(sema.db, module))
         }
+        hir::ModuleDef::Function(f) => {
+            let assoc = f.as_assoc_item(sema.db)?;
+            let name = assoc.name(sema.db)?;
+            let trait_ = assoc.containing_trait(sema.db)?;
+            impls_for_trait_fn(&sema, trait_, name)
+        }
         _ => return None,
     };
     Some(RangeInfo { range: node.syntax().text_range(), info: navs })
@@ -64,6 +71,23 @@ fn impls_for_trait(sema: &Semantics<RootDatabase>, trait_: hir::Trait) -> Vec<Na
         .collect()
 }
 
+fn impls_for_trait_fn(
+    sema: &Semantics<RootDatabase>,
+    trait_: hir::Trait,
+    fun_name: hir::Name,
+) -> Vec<NavigationTarget> {
+    Impl::all_for_trait(sema.db, trait_)
+        .into_iter()
+        .filter_map(|imp| {
+            let item = imp.items(sema.db).iter().find_map(|itm| {
+                let itm_name = itm.name(sema.db)?;
+                (itm_name == fun_name).then(|| itm.clone())
+            })?;
+            item.try_to_nav(sema.db)
+        })
+        .collect()
+}
+
 #[cfg(test)]
 mod tests {
     use ide_db::base_db::FileRange;
@@ -259,6 +283,26 @@ fn foo(_: bool$0) {{}}
 #[lang = "bool"]
 impl bool {}
    //^^^^
+"#,
+        );
+    }
+
+    #[test]
+    fn goto_implementation_trait_functions() {
+        check(
+            r#"
+trait Tr {
+    fn f$0();
+}
+
+struct S;
+
+impl Tr for S {
+    fn f() {
+     //^
+        println!("Hello, world!");
+    }
+}
 "#,
         );
     }
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 4d88932ca5..b2317618a8 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,4 +1,4 @@
-//! Advertises the capabilities of the LSP Server.
+//! Advertizes the capabilities of the LSP Server.
 use std::env;
 
 use lsp_types::{
@@ -54,7 +54,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
         code_action_provider: Some(code_action_capabilities(client_caps)),
         code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
         document_formatting_provider: Some(OneOf::Left(true)),
-        document_range_formatting_provider: Some(OneOf::Left(true)),
+        document_range_formatting_provider: None,
         document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
             first_trigger_character: "=".to_string(),
             more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7620a2fe17..7c02a507cd 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -218,10 +218,6 @@ config_data! {
         /// Advanced option, fully override the command rust-analyzer uses for
         /// formatting.
         rustfmt_overrideCommand: Option<Vec<String>> = "null",
-        /// Enables the use of rustfmt's unstable range formatting command for the
-        /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
-        /// available on a nightly build.
-        rustfmt_enableRangeFormatting: bool = "false",
 
         /// Workspace symbol search scope.
         workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"",
@@ -309,7 +305,7 @@ pub struct NotificationsConfig {
 
 #[derive(Debug, Clone)]
 pub enum RustfmtConfig {
-    Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
+    Rustfmt { extra_args: Vec<String> },
     CustomCommand { command: String, args: Vec<String> },
 }
 
@@ -588,10 +584,9 @@ impl Config {
                 let command = args.remove(0);
                 RustfmtConfig::CustomCommand { command, args }
             }
-            Some(_) | None => RustfmtConfig::Rustfmt {
-                extra_args: self.data.rustfmt_extraArgs.clone(),
-                enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
-            },
+            Some(_) | None => {
+                RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() }
+            }
         }
     }
     pub fn flycheck(&self) -> Option<FlycheckConfig> {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 4567446036..f482104248 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -27,7 +27,7 @@ use lsp_types::{
 };
 use project_model::TargetKind;
 use serde::{Deserialize, Serialize};
-use serde_json::{json, to_value};
+use serde_json::to_value;
 use stdx::format_to;
 use syntax::{algo, ast, AstNode, TextRange, TextSize};
 
@@ -955,17 +955,104 @@ pub(crate) fn handle_formatting(
     params: DocumentFormattingParams,
 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
     let _p = profile::span("handle_formatting");
+    let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+    let file = snap.analysis.file_text(file_id)?;
+    let crate_ids = snap.analysis.crate_for(file_id)?;
 
-    run_rustfmt(&snap, params.text_document, None)
-}
+    let line_index = snap.file_line_index(file_id)?;
 
-pub(crate) fn handle_range_formatting(
-    snap: GlobalStateSnapshot,
-    params: lsp_types::DocumentRangeFormattingParams,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
-    let _p = profile::span("handle_range_formatting");
+    let mut rustfmt = match snap.config.rustfmt() {
+        RustfmtConfig::Rustfmt { extra_args } => {
+            let mut cmd = process::Command::new(toolchain::rustfmt());
+            cmd.args(extra_args);
+            // try to chdir to the file so we can respect `rustfmt.toml`
+            // FIXME: use `rustfmt --config-path` once
+            // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
+            match params.text_document.uri.to_file_path() {
+                Ok(mut path) => {
+                    // pop off file name
+                    if path.pop() && path.is_dir() {
+                        cmd.current_dir(path);
+                    }
+                }
+                Err(_) => {
+                    log::error!(
+                        "Unable to get file path for {}, rustfmt.toml might be ignored",
+                        params.text_document.uri
+                    );
+                }
+            }
+            if let Some(&crate_id) = crate_ids.first() {
+                // Assume all crates are in the same edition
+                let edition = snap.analysis.crate_edition(crate_id)?;
+                cmd.arg("--edition");
+                cmd.arg(edition.to_string());
+            }
+            cmd
+        }
+        RustfmtConfig::CustomCommand { command, args } => {
+            let mut cmd = process::Command::new(command);
+            cmd.args(args);
+            cmd
+        }
+    };
+
+    let mut rustfmt =
+        rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
+
+    rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
 
-    run_rustfmt(&snap, params.text_document, Some(params.range))
+    let output = rustfmt.wait_with_output()?;
+    let captured_stdout = String::from_utf8(output.stdout)?;
+    let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
+
+    if !output.status.success() {
+        let rustfmt_not_installed =
+            captured_stderr.contains("not installed") || captured_stderr.contains("not available");
+
+        return match output.status.code() {
+            Some(1) if !rustfmt_not_installed => {
+                // While `rustfmt` doesn't have a specific exit code for parse errors this is the
+                // likely cause exiting with 1. Most Language Servers swallow parse errors on
+                // formatting because otherwise an error is surfaced to the user on top of the
+                // syntax error diagnostics they're already receiving. This is especially jarring
+                // if they have format on save enabled.
+                log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
+                Ok(None)
+            }
+            _ => {
+                // Something else happened - e.g. `rustfmt` is missing or caught a signal
+                Err(LspError::new(
+                    -32900,
+                    format!(
+                        r#"rustfmt exited with:
+                           Status: {}
+                           stdout: {}
+                           stderr: {}"#,
+                        output.status, captured_stdout, captured_stderr,
+                    ),
+                )
+                .into())
+            }
+        };
+    }
+
+    let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
+
+    if line_index.endings != new_line_endings {
+        // If line endings are different, send the entire file.
+        // Diffing would not work here, as the line endings might be the only
+        // difference.
+        Ok(Some(to_proto::text_edit_vec(
+            &line_index,
+            TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
+        )))
+    } else if *file == new_text {
+        // The document is already formatted correctly -- no edits needed.
+        Ok(None)
+    } else {
+        Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
+    }
 }
 
 pub(crate) fn handle_code_action(
@@ -1588,140 +1675,6 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
     }
 }
 
-fn run_rustfmt(
-    snap: &GlobalStateSnapshot,
-    text_document: TextDocumentIdentifier,
-    range: Option<lsp_types::Range>,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
-    let file_id = from_proto::file_id(&snap, &text_document.uri)?;
-    let file = snap.analysis.file_text(file_id)?;
-    let crate_ids = snap.analysis.crate_for(file_id)?;
-
-    let line_index = snap.file_line_index(file_id)?;
-
-    let mut rustfmt = match snap.config.rustfmt() {
-        RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
-            let mut cmd = process::Command::new(toolchain::rustfmt());
-            cmd.args(extra_args);
-            // try to chdir to the file so we can respect `rustfmt.toml`
-            // FIXME: use `rustfmt --config-path` once
-            // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
-            match text_document.uri.to_file_path() {
-                Ok(mut path) => {
-                    // pop off file name
-                    if path.pop() && path.is_dir() {
-                        cmd.current_dir(path);
-                    }
-                }
-                Err(_) => {
-                    log::error!(
-                        "Unable to get file path for {}, rustfmt.toml might be ignored",
-                        text_document.uri
-                    );
-                }
-            }
-            if let Some(&crate_id) = crate_ids.first() {
-                // Assume all crates are in the same edition
-                let edition = snap.analysis.crate_edition(crate_id)?;
-                cmd.arg("--edition");
-                cmd.arg(edition.to_string());
-            }
-
-            if let Some(range) = range {
-                if !enable_range_formatting {
-                    return Err(LspError::new(
-                        ErrorCode::InvalidRequest as i32,
-                        String::from(
-                            "rustfmt range formatting is unstable. \
-                            Opt-in by using a nightly build of rustfmt and setting \
-                            `rustfmt.enableRangeFormatting` to true in your LSP configuration",
-                        ),
-                    )
-                    .into());
-                }
-
-                let frange = from_proto::file_range(&snap, text_document.clone(), range)?;
-                let start_line = line_index.index.line_col(frange.range.start()).line;
-                let end_line = line_index.index.line_col(frange.range.end()).line;
-
-                cmd.arg("--unstable-features");
-                cmd.arg("--file-lines");
-                cmd.arg(
-                    json!([{
-                        "file": "stdin",
-                        "range": [start_line, end_line]
-                    }])
-                    .to_string(),
-                );
-            }
-
-            cmd
-        }
-        RustfmtConfig::CustomCommand { command, args } => {
-            let mut cmd = process::Command::new(command);
-            cmd.args(args);
-            cmd
-        }
-    };
-
-    let mut rustfmt =
-        rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
-
-    rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
-
-    let output = rustfmt.wait_with_output()?;
-    let captured_stdout = String::from_utf8(output.stdout)?;
-    let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
-
-    if !output.status.success() {
-        let rustfmt_not_installed =
-            captured_stderr.contains("not installed") || captured_stderr.contains("not available");
-
-        return match output.status.code() {
-            Some(1) if !rustfmt_not_installed => {
-                // While `rustfmt` doesn't have a specific exit code for parse errors this is the
-                // likely cause exiting with 1. Most Language Servers swallow parse errors on
-                // formatting because otherwise an error is surfaced to the user on top of the
-                // syntax error diagnostics they're already receiving. This is especially jarring
-                // if they have format on save enabled.
-                log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
-                Ok(None)
-            }
-            _ => {
-                // Something else happened - e.g. `rustfmt` is missing or caught a signal
-                Err(LspError::new(
-                    -32900,
-                    format!(
-                        r#"rustfmt exited with:
-                           Status: {}
-                           stdout: {}
-                           stderr: {}"#,
-                        output.status, captured_stdout, captured_stderr,
-                    ),
-                )
-                .into())
-            }
-        };
-    }
-
-    let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
-
-    if line_index.endings != new_line_endings {
-        // If line endings are different, send the entire file.
-        // Diffing would not work here, as the line endings might be the only
-        // difference.
-        Ok(Some(to_proto::text_edit_vec(
-            &line_index,
-            TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
-        )))
-    } else if *file == new_text {
-        // The document is already formatted correctly -- no edits needed.
-        Ok(None)
-    } else {
-        Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
-    }
-}
-
 #[derive(Debug, Serialize, Deserialize)]
 struct CompletionResolveData {
     position: lsp_types::TextDocumentPositionParams,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 008758ea0d..cb002f7009 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -543,7 +543,6 @@ impl GlobalState {
             .on::<lsp_types::request::Rename>(handlers::handle_rename)
             .on::<lsp_types::request::References>(handlers::handle_references)
             .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
-            .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
             .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
             .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
             .on::<lsp_types::request::CallHierarchyIncomingCalls>(
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index f3da82febb..c02bab7cc3 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -346,13 +346,6 @@ Additional arguments to `rustfmt`.
 Advanced option, fully override the command rust-analyzer uses for
 formatting.
 --
-[[rust-analyzer.rustfmt.enableRangeFormatting]]rust-analyzer.rustfmt.enableRangeFormatting (default: `false`)::
-+
---
-Enables the use of rustfmt's unstable range formatting command for the
-`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
-available on a nightly build.
---
 [[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 05cbccf946..17d9281ff7 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -795,11 +795,6 @@
                         "type": "string"
                     }
                 },
-                "rust-analyzer.rustfmt.enableRangeFormatting": {
-                    "markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
-                    "default": false,
-                    "type": "boolean"
-                },
                 "rust-analyzer.workspace.symbol.search.scope": {
                     "markdownDescription": "Workspace symbol search scope.",
                     "default": "workspace",


Test patch:
diff --git a/crates/hir_ty/src/tests/coercion.rs b/crates/hir_ty/src/tests/coercion.rs
index 6dac7e1039..bb568ea372 100644
--- a/crates/hir_ty/src/tests/coercion.rs
+++ b/crates/hir_ty/src/tests/coercion.rs
@@ -832,9 +832,11 @@ fn coerce_unsize_super_trait_cycle() {
     );
 }
 
+#[ignore]
 #[test]
 fn coerce_unsize_generic() {
-    // FIXME: fix the type mismatches here
+    // FIXME: Implement this
+    // https://doc.rust-lang.org/reference/type-coercions.html#unsized-coercions
     check_infer_with_mismatches(
         r#"
         #[lang = "unsize"]
@@ -852,58 +854,8 @@ fn coerce_unsize_generic() {
             let _: &Bar<[usize]> = &Bar(Foo { t: [1, 2, 3] });
         }
         "#,
-        expect![[r#"
-            209..317 '{     ... }); }': ()
-            219..220 '_': &Foo<[usize]>
-            238..259 '&Foo {..., 3] }': &Foo<[usize]>
-            239..259 'Foo { ..., 3] }': Foo<[usize]>
-            248..257 '[1, 2, 3]': [usize; 3]
-            249..250 '1': usize
-            252..253 '2': usize
-            255..256 '3': usize
-            269..270 '_': &Bar<[usize]>
-            288..314 '&Bar(F... 3] })': &Bar<[i32; 3]>
-            289..292 'Bar': Bar<[i32; 3]>(Foo<[i32; 3]>) -> Bar<[i32; 3]>
-            289..314 'Bar(Fo... 3] })': Bar<[i32; 3]>
-            293..313 'Foo { ..., 3] }': Foo<[i32; 3]>
-            302..311 '[1, 2, 3]': [i32; 3]
-            303..304 '1': i32
-            306..307 '2': i32
-            309..310 '3': i32
-            248..257: expected [usize], got [usize; 3]
-            288..314: expected &Bar<[usize]>, got &Bar<[i32; 3]>
-        "#]],
-    );
-}
-
-#[test]
-fn coerce_unsize_apit() {
-    // FIXME: #8984
-    check_infer_with_mismatches(
-        r#"
-#[lang = "sized"]
-pub trait Sized {}
-#[lang = "unsize"]
-pub trait Unsize<T> {}
-#[lang = "coerce_unsized"]
-pub trait CoerceUnsized<T> {}
-
-impl<T: Unsize<U>, U> CoerceUnsized<&U> for &T {}
-
-trait Foo {}
-
-fn test(f: impl Foo) {
-    let _: &dyn Foo = &f;
-}
-        "#,
-        expect![[r#"
-            210..211 'f': impl Foo
-            223..252 '{     ... &f; }': ()
-            233..234 '_': &dyn Foo
-            247..249 '&f': &impl Foo
-            248..249 'f': impl Foo
-            247..249: expected &dyn Foo, got &impl Foo
-        "#]],
+        expect![[r"
+        "]],
     );
 }
 
@@ -960,46 +912,3 @@ fn test() -> i32 {
         "#,
     )
 }
-
-#[test]
-fn panic_macro() {
-    check_infer_with_mismatches(
-        r#"
-mod panic {
-    #[macro_export]
-    pub macro panic_2015 {
-        () => (
-            $crate::panicking::panic()
-        ),
-    }
-}
-
-mod panicking {
-    pub fn panic() -> ! { loop {} }
-}
-
-#[rustc_builtin_macro = "core_panic"]
-macro_rules! panic {
-    // Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021`
-    // depending on the edition of the caller.
-    ($($arg:tt)*) => {
-        /* compiler built-in */
-    };
-}
-
-fn main() {
-    panic!()
-}
-        "#,
-        expect![[r#"
-            174..185 '{ loop {} }': !
-            176..183 'loop {}': !
-            181..183 '{}': ()
-            !0..24 '$crate...:panic': fn panic() -> !
-            !0..26 '$crate...anic()': !
-            !0..26 '$crate...anic()': !
-            !0..28 '$crate...015!()': !
-            454..470 '{     ...c!() }': ()
-        "#]],
-    );
-}