Repository: rust-analyzer/rust-analyzer
Pull request #8996 (2021-05-25 23:14:41)

Fix bug where library functions were not highlighted as such
Sorry about forgetting to test this in my last PR.

Veykril: bors d+
bors[bot]: :v: arzg can now approve this pull request. To approve and merge a pull request, simply reply with `bors r+`. More detailed instructions are available [here](https://bors.tech/documentation/getting-started/#reviewing-pull-requests).

Patch:
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 22ec7c6aca..414c3f35ee 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -3,227 +3,13 @@
 //!
 //! This probably isn't the best way to do this -- ideally, diagnistics should
 //! be expressed in terms of hir types themselves.
-use std::any::Any;
-
-use cfg::{CfgExpr, CfgOptions, DnfExpr};
-use hir_def::path::ModPath;
-use hir_expand::{HirFileId, InFile};
-use stdx::format_to;
-use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
-
-pub use hir_ty::{
-    diagnostics::{
-        IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
-        MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
-        ReplaceFilterMapNextWithFindMap,
-    },
-    diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
+pub use hir_def::diagnostics::{
+    InactiveCode, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
+};
+pub use hir_expand::diagnostics::{
+    Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
+};
+pub use hir_ty::diagnostics::{
+    IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
+    NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
 };
-
-// Diagnostic: unresolved-module
-//
-// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
-#[derive(Debug)]
-pub struct UnresolvedModule {
-    pub file: HirFileId,
-    pub decl: AstPtr<ast::Module>,
-    pub candidate: String,
-}
-
-impl Diagnostic for UnresolvedModule {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-module")
-    }
-    fn message(&self) -> String {
-        "unresolved module".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.decl.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: unresolved-extern-crate
-//
-// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
-#[derive(Debug)]
-pub struct UnresolvedExternCrate {
-    pub file: HirFileId,
-    pub item: AstPtr<ast::ExternCrate>,
-}
-
-impl Diagnostic for UnresolvedExternCrate {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-extern-crate")
-    }
-    fn message(&self) -> String {
-        "unresolved extern crate".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.item.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-#[derive(Debug)]
-pub struct UnresolvedImport {
-    pub file: HirFileId,
-    pub node: AstPtr<ast::UseTree>,
-}
-
-impl Diagnostic for UnresolvedImport {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-import")
-    }
-    fn message(&self) -> String {
-        "unresolved import".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        // This currently results in false positives in the following cases:
-        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
-        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
-        // - proc macros and/or proc macro generated code
-        true
-    }
-}
-
-// Diagnostic: unresolved-macro-call
-//
-// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
-// macro in a macro invocation.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct UnresolvedMacroCall {
-    pub file: HirFileId,
-    pub node: AstPtr<ast::MacroCall>,
-    pub path: ModPath,
-}
-
-impl Diagnostic for UnresolvedMacroCall {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-macro-call")
-    }
-    fn message(&self) -> String {
-        format!("unresolved macro `{}!`", self.path)
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        true
-    }
-}
-
-// Diagnostic: inactive-code
-//
-// This diagnostic is shown for code with inactive `#[cfg]` attributes.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct InactiveCode {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    pub cfg: CfgExpr,
-    pub opts: CfgOptions,
-}
-
-impl Diagnostic for InactiveCode {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("inactive-code")
-    }
-    fn message(&self) -> String {
-        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
-        let mut buf = "code is inactive due to #[cfg] directives".to_string();
-
-        if let Some(inactive) = inactive {
-            format_to!(buf, ": {}", inactive);
-        }
-
-        buf
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: unresolved-proc-macro
-//
-// This diagnostic is shown when a procedural macro can not be found. This usually means that
-// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
-// but can also indicate project setup problems.
-//
-// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
-// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
-// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct UnresolvedProcMacro {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
-    /// to use instead.
-    pub precise_location: Option<TextRange>,
-    pub macro_name: Option<String>,
-}
-
-impl Diagnostic for UnresolvedProcMacro {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-proc-macro")
-    }
-
-    fn message(&self) -> String {
-        match &self.macro_name {
-            Some(name) => format!("proc macro `{}` not expanded", name),
-            None => "proc macro not expanded".to_string(),
-        }
-    }
-
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: macro-error
-//
-// This diagnostic is shown for macro expansion errors.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct MacroError {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    pub message: String,
-}
-
-impl Diagnostic for MacroError {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("macro-error")
-    }
-    fn message(&self) -> String {
-        self.message.clone()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        // Newly added and not very well-tested, might contain false positives.
-        true
-    }
-}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1ecd2391b0..cdf65a0445 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -35,18 +35,12 @@ use std::{iter, sync::Arc};
 
 use arrayvec::ArrayVec;
 use base_db::{CrateDisplayName, CrateId, Edition, FileId};
-use diagnostics::{
-    InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
-    UnresolvedModule, UnresolvedProcMacro,
-};
 use either::Either;
 use hir_def::{
     adt::{ReprKind, VariantData},
-    body::BodyDiagnostic,
     expr::{BindingAnnotation, LabelId, Pat, PatId},
     item_tree::ItemTreeNode,
     lang_item::LangItemTarget,
-    nameres,
     per_ns::PerNs,
     resolver::{HasResolver, Resolver},
     src::HasSource as _,
@@ -56,12 +50,11 @@ use hir_def::{
     LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
     TypeParamId, UnionId,
 };
-use hir_expand::{name::name, MacroCallKind, MacroDefKind};
+use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
 use hir_ty::{
     autoderef,
     consteval::ConstExt,
     could_unify,
-    diagnostics_sink::DiagnosticSink,
     method_resolution::{self, def_crates, TyFingerprint},
     primitive::UintTy,
     subst_prefix,
@@ -72,12 +65,11 @@ use hir_ty::{
     WhereClause,
 };
 use itertools::Itertools;
-use nameres::diagnostics::DefDiagnosticKind;
 use rustc_hash::FxHashSet;
 use stdx::{format_to, impl_from};
 use syntax::{
     ast::{self, AttrsOwner, NameOwner},
-    AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr,
+    AstNode, SmolStr,
 };
 use tt::{Ident, Leaf, Literal, TokenTree};
 
@@ -450,137 +442,7 @@ impl Module {
             format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
         });
         let def_map = self.id.def_map(db.upcast());
-        for diag in def_map.diagnostics() {
-            if diag.in_module != self.id.local_id {
-                // FIXME: This is accidentally quadratic.
-                continue;
-            }
-            match &diag.kind {
-                DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => {
-                    let decl = declaration.to_node(db.upcast());
-                    sink.push(UnresolvedModule {
-                        file: declaration.file_id,
-                        decl: AstPtr::new(&decl),
-                        candidate: candidate.clone(),
-                    })
-                }
-                DefDiagnosticKind::UnresolvedExternCrate { ast } => {
-                    let item = ast.to_node(db.upcast());
-                    sink.push(UnresolvedExternCrate {
-                        file: ast.file_id,
-                        item: AstPtr::new(&item),
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedImport { ast, index } => {
-                    let use_item = ast.to_node(db.upcast());
-                    let hygiene = Hygiene::new(db.upcast(), ast.file_id);
-                    let mut cur = 0;
-                    let mut tree = None;
-                    ModPath::expand_use_item(
-                        db.upcast(),
-                        InFile::new(ast.file_id, use_item),
-                        &hygiene,
-                        |_mod_path, use_tree, _is_glob, _alias| {
-                            if cur == *index {
-                                tree = Some(use_tree.clone());
-                            }
-
-                            cur += 1;
-                        },
-                    );
-
-                    if let Some(tree) = tree {
-                        sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
-                    }
-                }
-
-                DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
-                    let item = ast.to_node(db.upcast());
-                    sink.push(InactiveCode {
-                        file: ast.file_id,
-                        node: AstPtr::new(&item).into(),
-                        cfg: cfg.clone(),
-                        opts: opts.clone(),
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedProcMacro { ast } => {
-                    let mut precise_location = None;
-                    let (file, ast, name) = match ast {
-                        MacroCallKind::FnLike { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
-                        }
-                        MacroCallKind::Derive { ast_id, derive_name, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-
-                            // Compute the precise location of the macro name's token in the derive
-                            // list.
-                            // FIXME: This does not handle paths to the macro, but neither does the
-                            // rest of r-a.
-                            let derive_attrs =
-                                node.attrs().filter_map(|attr| match attr.as_simple_call() {
-                                    Some((name, args)) if name == "derive" => Some(args),
-                                    _ => None,
-                                });
-                            'outer: for attr in derive_attrs {
-                                let tokens =
-                                    attr.syntax().children_with_tokens().filter_map(|elem| {
-                                        match elem {
-                                            syntax::NodeOrToken::Node(_) => None,
-                                            syntax::NodeOrToken::Token(tok) => Some(tok),
-                                        }
-                                    });
-                                for token in tokens {
-                                    if token.kind() == SyntaxKind::IDENT
-                                        && token.text() == derive_name.as_str()
-                                    {
-                                        precise_location = Some(token.text_range());
-                                        break 'outer;
-                                    }
-                                }
-                            }
-
-                            (
-                                ast_id.file_id,
-                                SyntaxNodePtr::from(AstPtr::new(&node)),
-                                Some(derive_name.clone()),
-                            )
-                        }
-                    };
-                    sink.push(UnresolvedProcMacro {
-                        file,
-                        node: ast,
-                        precise_location,
-                        macro_name: name,
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
-                    let node = ast.to_node(db.upcast());
-                    sink.push(UnresolvedMacroCall {
-                        file: ast.file_id,
-                        node: AstPtr::new(&node),
-                        path: path.clone(),
-                    });
-                }
-
-                DefDiagnosticKind::MacroError { ast, message } => {
-                    let (file, ast) = match ast {
-                        MacroCallKind::FnLike { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
-                        }
-                        MacroCallKind::Derive { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
-                        }
-                    };
-                    sink.push(MacroError { file, node: ast, message: message.clone() });
-                }
-            }
-        }
+        def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
         for decl in self.declarations(db) {
             match decl {
                 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
@@ -1003,37 +865,7 @@ impl Function {
 
     pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
         let krate = self.module(db).id.krate();
-
-        let source_map = db.body_with_source_map(self.id.into()).1;
-        for diag in source_map.diagnostics() {
-            match diag {
-                BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode {
-                    file: node.file_id,
-                    node: node.value.clone(),
-                    cfg: cfg.clone(),
-                    opts: opts.clone(),
-                }),
-                BodyDiagnostic::MacroError { node, message } => sink.push(MacroError {
-                    file: node.file_id,
-                    node: node.value.clone().into(),
-                    message: message.to_string(),
-                }),
-                BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro {
-                    file: node.file_id,
-                    node: node.value.clone().into(),
-                    precise_location: None,
-                    macro_name: None,
-                }),
-                BodyDiagnostic::UnresolvedMacroCall { node, path } => {
-                    sink.push(UnresolvedMacroCall {
-                        file: node.file_id,
-                        node: node.value.clone(),
-                        path: path.clone(),
-                    })
-                }
-            }
-        }
-
+        hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
         hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
         hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
     }
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index c521879c87..98b485b602 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,6 +1,7 @@
 //! Defines `Body`: a lowered representation of bodies of functions, statics and
 //! consts.
 mod lower;
+mod diagnostics;
 #[cfg(test)]
 mod tests;
 pub mod scope;
@@ -8,16 +9,17 @@ pub mod scope;
 use std::{mem, ops::Index, sync::Arc};
 
 use base_db::CrateId;
-use cfg::{CfgExpr, CfgOptions};
+use cfg::CfgOptions;
 use drop_bomb::DropBomb;
 use either::Either;
 use hir_expand::{
-    ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
+    ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult,
+    HirFileId, InFile, MacroDefId,
 };
 use la_arena::{Arena, ArenaMap};
 use profile::Count;
 use rustc_hash::FxHashMap;
-use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
+use syntax::{ast, AstNode, AstPtr};
 
 use crate::{
     attr::{Attrs, RawAttrs},
@@ -271,20 +273,12 @@ pub struct BodySourceMap {
 
     /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
     /// the source map (since they're just as volatile).
-    diagnostics: Vec<BodyDiagnostic>,
+    diagnostics: Vec<diagnostics::BodyDiagnostic>,
 }
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
 
-#[derive(Debug, Eq, PartialEq)]
-pub enum BodyDiagnostic {
-    InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
-    MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
-    UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
-    UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
-}
-
 impl Body {
     pub(crate) fn body_with_source_map_query(
         db: &dyn DefDatabase,
@@ -422,8 +416,9 @@ impl BodySourceMap {
         self.field_map.get(&src).cloned()
     }
 
-    /// Get a reference to the body source map's diagnostics.
-    pub fn diagnostics(&self) -> &[BodyDiagnostic] {
-        &self.diagnostics
+    pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
+        for diag in &self.diagnostics {
+            diag.add_to(sink);
+        }
     }
 }
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
new file mode 100644
index 0000000000..f6992c9a8a
--- /dev/null
+++ b/crates/hir_def/src/body/diagnostics.rs
@@ -0,0 +1,32 @@
+//! Diagnostics emitted during body lowering.
+
+use hir_expand::diagnostics::DiagnosticSink;
+
+use crate::diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro};
+
+#[derive(Debug, Eq, PartialEq)]
+pub(crate) enum BodyDiagnostic {
+    InactiveCode(InactiveCode),
+    MacroError(MacroError),
+    UnresolvedProcMacro(UnresolvedProcMacro),
+    UnresolvedMacroCall(UnresolvedMacroCall),
+}
+
+impl BodyDiagnostic {
+    pub(crate) fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
+        match self {
+            BodyDiagnostic::InactiveCode(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::MacroError(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::UnresolvedProcMacro(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::UnresolvedMacroCall(diag) => {
+                sink.push(diag.clone());
+            }
+        }
+    }
+}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index da1fdac33a..2a7e0205f5 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -8,7 +8,7 @@ use hir_expand::{
     ast_id_map::{AstIdMap, FileAstId},
     hygiene::Hygiene,
     name::{name, AsName, Name},
-    ExpandError, HirFileId, InFile,
+    ExpandError, HirFileId,
 };
 use la_arena::Arena;
 use profile::Count;
@@ -23,9 +23,9 @@ use syntax::{
 use crate::{
     adt::StructKind,
     body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax},
-    body::{BodyDiagnostic, ExprSource, PatSource},
     builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
     db::DefDatabase,
+    diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro},
     expr::{
         dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Label,
         LabelId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField,
@@ -38,6 +38,8 @@ use crate::{
     AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
 };
 
+use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
+
 pub struct LowerCtx<'a> {
     pub db: &'a dyn DefDatabase,
     hygiene: Hygiene,
@@ -590,10 +592,13 @@ impl ExprCollector<'_> {
         let res = match res {
             Ok(res) => res,
             Err(UnresolvedMacro { path }) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall {
-                    node: InFile::new(outer_file, syntax_ptr),
-                    path,
-                });
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall(
+                    UnresolvedMacroCall {
+                        file: outer_file,
+                        node: syntax_ptr.cast().unwrap(),
+                        path,
+                    },
+                ));
                 collector(self, None);
                 return;
             }
@@ -601,15 +606,21 @@ impl ExprCollector<'_> {
 
         match &res.err {
             Some(ExpandError::UnresolvedProcMacro) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
-                    node: InFile::new(outer_file, syntax_ptr),
-                });
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro(
+                    UnresolvedProcMacro {
+                        file: outer_file,
+                        node: syntax_ptr.into(),
+                        precise_location: None,
+                        macro_name: None,
+                    },
+                ));
             }
             Some(err) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
-                    node: InFile::new(outer_file, syntax_ptr),
+                self.source_map.diagnostics.push(BodyDiagnostic::MacroError(MacroError {
+                    file: outer_file,
+                    node: syntax_ptr.into(),
                     message: err.to_string(),
-                });
+                }));
             }
             None => {}
         }
@@ -934,14 +945,12 @@ impl ExprCollector<'_> {
                     return Some(());
                 }
 
-                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode {
-                    node: InFile::new(
-                        self.expander.current_file_id,
-                        SyntaxNodePtr::new(owner.syntax()),
-                    ),
+                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
+                    file: self.expander.current_file_id,
+                    node: SyntaxNodePtr::new(owner.syntax()),
                     cfg,
                     opts: self.expander.cfg_options().clone(),
-                });
+                }));
 
                 None
             }
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
new file mode 100644
index 0000000000..a71ae26684
--- /dev/null
+++ b/crates/hir_def/src/diagnostics.rs
@@ -0,0 +1,227 @@
+//! Diagnostics produced by `hir_def`.
+
+use std::any::Any;
+use stdx::format_to;
+
+use cfg::{CfgExpr, CfgOptions, DnfExpr};
+use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
+use hir_expand::{HirFileId, InFile};
+use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
+
+use crate::{db::DefDatabase, path::ModPath, DefWithBodyId};
+
+pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
+    let source_map = db.body_with_source_map(owner).1;
+    source_map.add_diagnostics(db, sink);
+}
+
+// Diagnostic: unresolved-module
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
+#[derive(Debug)]
+pub struct UnresolvedModule {
+    pub file: HirFileId,
+    pub decl: AstPtr<ast::Module>,
+    pub candidate: String,
+}
+
+impl Diagnostic for UnresolvedModule {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-module")
+    }
+    fn message(&self) -> String {
+        "unresolved module".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.decl.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-extern-crate
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
+#[derive(Debug)]
+pub struct UnresolvedExternCrate {
+    pub file: HirFileId,
+    pub item: AstPtr<ast::ExternCrate>,
+}
+
+impl Diagnostic for UnresolvedExternCrate {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-extern-crate")
+    }
+    fn message(&self) -> String {
+        "unresolved extern crate".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.item.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-import
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
+#[derive(Debug)]
+pub struct UnresolvedImport {
+    pub file: HirFileId,
+    pub node: AstPtr<ast::UseTree>,
+}
+
+impl Diagnostic for UnresolvedImport {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-import")
+    }
+    fn message(&self) -> String {
+        "unresolved import".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        // This currently results in false positives in the following cases:
+        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
+        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
+        // - proc macros and/or proc macro generated code
+        true
+    }
+}
+
+// Diagnostic: unresolved-macro-call
+//
+// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
+// macro in a macro invocation.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedMacroCall {
+    pub file: HirFileId,
+    pub node: AstPtr<ast::MacroCall>,
+    pub path: ModPath,
+}
+
+impl Diagnostic for UnresolvedMacroCall {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-macro-call")
+    }
+    fn message(&self) -> String {
+        format!("unresolved macro `{}!`", self.path)
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        true
+    }
+}
+
+// Diagnostic: inactive-code
+//
+// This diagnostic is shown for code with inactive `#[cfg]` attributes.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct InactiveCode {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    pub cfg: CfgExpr,
+    pub opts: CfgOptions,
+}
+
+impl Diagnostic for InactiveCode {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("inactive-code")
+    }
+    fn message(&self) -> String {
+        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
+        let mut buf = "code is inactive due to #[cfg] directives".to_string();
+
+        if let Some(inactive) = inactive {
+            format_to!(buf, ": {}", inactive);
+        }
+
+        buf
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-proc-macro
+//
+// This diagnostic is shown when a procedural macro can not be found. This usually means that
+// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
+// but can also indicate project setup problems.
+//
+// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
+// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
+// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedProcMacro {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
+    /// to use instead.
+    pub precise_location: Option<TextRange>,
+    pub macro_name: Option<String>,
+}
+
+impl Diagnostic for UnresolvedProcMacro {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-proc-macro")
+    }
+
+    fn message(&self) -> String {
+        match &self.macro_name {
+            Some(name) => format!("proc macro `{}` not expanded", name),
+            None => "proc macro not expanded".to_string(),
+        }
+    }
+
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: macro-error
+//
+// This diagnostic is shown for macro expansion errors.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroError {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    pub message: String,
+}
+
+impl Diagnostic for MacroError {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("macro-error")
+    }
+    fn message(&self) -> String {
+        self.message.clone()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        // Newly added and not very well-tested, might contain false positives.
+        true
+    }
+}
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 9aa95720af..70001cac86 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -19,6 +19,7 @@ pub mod path;
 pub mod type_ref;
 pub mod builtin_type;
 pub mod builtin_attr;
+pub mod diagnostics;
 pub mod per_ns;
 pub mod item_scope;
 
@@ -55,6 +56,7 @@ use std::{
     sync::Arc,
 };
 
+use adt::VariantData;
 use base_db::{impl_intern_key, salsa, CrateId};
 use hir_expand::{
     ast_id_map::FileAstId,
@@ -65,18 +67,15 @@ use hir_expand::{
 use la_arena::Idx;
 use nameres::DefMap;
 use path::ModPath;
-use stdx::impl_from;
 use syntax::ast;
 
-use crate::{
-    adt::VariantData,
-    attr::AttrId,
-    builtin_type::BuiltinType,
-    item_tree::{
-        Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
-        TypeAlias, Union,
-    },
+use crate::attr::AttrId;
+use crate::builtin_type::BuiltinType;
+use item_tree::{
+    Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
+    TypeAlias, Union,
 };
+use stdx::impl_from;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ModuleId {
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index ebfcc26c41..249af6fc80 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -47,19 +47,18 @@
 //! path and, upon success, we run macro expansion and "collect module" phase on
 //! the result
 
-pub mod diagnostics;
 mod collector;
 mod mod_resolution;
 mod path_resolution;
-mod proc_macro;
 
 #[cfg(test)]
 mod tests;
+mod proc_macro;
 
 use std::sync::Arc;
 
 use base_db::{CrateId, Edition, FileId};
-use hir_expand::{name::Name, InFile, MacroDefId};
+use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId};
 use la_arena::Arena;
 use profile::Count;
 use rustc_hash::FxHashMap;
@@ -255,6 +254,15 @@ impl DefMap {
         }
     }
 
+    pub fn add_diagnostics(
+        &self,
+        db: &dyn DefDatabase,
+        module: LocalModuleId,
+        sink: &mut DiagnosticSink,
+    ) {
+        self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
+    }
+
     pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ {
         self.modules
             .iter()
@@ -440,11 +448,6 @@ impl DefMap {
             module.scope.shrink_to_fit();
         }
     }
-
-    /// Get a reference to the def map's diagnostics.
-    pub fn diagnostics(&self) -> &[DefDiagnostic] {
-        self.diagnostics.as_slice()
-    }
 }
 
 impl ModuleData {
@@ -468,3 +471,236 @@ pub enum ModuleSource {
     Module(ast::Module),
     BlockExpr(ast::BlockExpr),
 }
+
+mod diagnostics {
+    use cfg::{CfgExpr, CfgOptions};
+    use hir_expand::diagnostics::DiagnosticSink;
+    use hir_expand::hygiene::Hygiene;
+    use hir_expand::{InFile, MacroCallKind};
+    use syntax::ast::AttrsOwner;
+    use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr};
+
+    use crate::path::ModPath;
+    use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
+
+    #[derive(Debug, PartialEq, Eq)]
+    enum DiagnosticKind {
+        UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
+
+        UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
+
+        UnresolvedImport { ast: AstId<ast::Use>, index: usize },
+
+        UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
+
+        UnresolvedProcMacro { ast: MacroCallKind },
+
+        UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
+
+        MacroError { ast: MacroCallKind, message: String },
+    }
+
+    #[derive(Debug, PartialEq, Eq)]
+    pub(super) struct DefDiagnostic {
+        in_module: LocalModuleId,
+        kind: DiagnosticKind,
+    }
+
+    impl DefDiagnostic {
+        pub(super) fn unresolved_module(
+            container: LocalModuleId,
+            declaration: AstId<ast::Module>,
+            candidate: String,
+        ) -> Self {
+            Self {
+                in_module: container,
+                kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
+            }
+        }
+
+        pub(super) fn unresolved_extern_crate(
+            container: LocalModuleId,
+            declaration: AstId<ast::ExternCrate>,
+        ) -> Self {
+            Self {
+                in_module: container,
+                kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
+            }
+        }
+
+        pub(super) fn unresolved_import(
+            container: LocalModuleId,
+            ast: AstId<ast::Use>,
+            index: usize,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
+        }
+
+        pub(super) fn unconfigured_code(
+            container: LocalModuleId,
+            ast: AstId<ast::Item>,
+            cfg: CfgExpr,
+            opts: CfgOptions,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
+        }
+
+        pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
+        }
+
+        pub(super) fn macro_error(
+            container: LocalModuleId,
+            ast: MacroCallKind,
+            message: String,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
+        }
+
+        pub(super) fn unresolved_macro_call(
+            container: LocalModuleId,
+            ast: AstId<ast::MacroCall>,
+            path: ModPath,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast, path } }
+        }
+
+        pub(super) fn add_to(
+            &self,
+            db: &dyn DefDatabase,
+            target_module: LocalModuleId,
+            sink: &mut DiagnosticSink,
+        ) {
+            if self.in_module != target_module {
+                return;
+            }
+
+            match &self.kind {
+                DiagnosticKind::UnresolvedModule { declaration, candidate } => {
+                    let decl = declaration.to_node(db.upcast());
+                    sink.push(UnresolvedModule {
+                        file: declaration.file_id,
+                        decl: AstPtr::new(&decl),
+                        candidate: candidate.clone(),
+                    })
+                }
+
+                DiagnosticKind::UnresolvedExternCrate { ast } => {
+                    let item = ast.to_node(db.upcast());
+                    sink.push(UnresolvedExternCrate {
+                        file: ast.file_id,
+                        item: AstPtr::new(&item),
+                    });
+                }
+
+                DiagnosticKind::UnresolvedImport { ast, index } => {
+                    let use_item = ast.to_node(db.upcast());
+                    let hygiene = Hygiene::new(db.upcast(), ast.file_id);
+                    let mut cur = 0;
+                    let mut tree = None;
+                    ModPath::expand_use_item(
+                        db,
+                        InFile::new(ast.file_id, use_item),
+                        &hygiene,
+                        |_mod_path, use_tree, _is_glob, _alias| {
+                            if cur == *index {
+                                tree = Some(use_tree.clone());
+                            }
+
+                            cur += 1;
+                        },
+                    );
+
+                    if let Some(tree) = tree {
+                        sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
+                    }
+                }
+
+                DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
+                    let item = ast.to_node(db.upcast());
+                    sink.push(InactiveCode {
+                        file: ast.file_id,
+                        node: AstPtr::new(&item).into(),
+                        cfg: cfg.clone(),
+                        opts: opts.clone(),
+                    });
+                }
+
+                DiagnosticKind::UnresolvedProcMacro { ast } => {
+                    let mut precise_location = None;
+                    let (file, ast, name) = match ast {
+                        MacroCallKind::FnLike { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
+                        }
+                        MacroCallKind::Derive { ast_id, derive_name, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+
+                            // Compute the precise location of the macro name's token in the derive
+                            // list.
+                            // FIXME: This does not handle paths to the macro, but neither does the
+                            // rest of r-a.
+                            let derive_attrs =
+                                node.attrs().filter_map(|attr| match attr.as_simple_call() {
+                                    Some((name, args)) if name == "derive" => Some(args),
+                                    _ => None,
+                                });
+                            'outer: for attr in derive_attrs {
+                                let tokens =
+                                    attr.syntax().children_with_tokens().filter_map(|elem| {
+                                        match elem {
+                                            syntax::NodeOrToken::Node(_) => None,
+                                            syntax::NodeOrToken::Token(tok) => Some(tok),
+                                        }
+                                    });
+                                for token in tokens {
+                                    if token.kind() == SyntaxKind::IDENT
+                                        && token.text() == derive_name.as_str()
+                                    {
+                                        precise_location = Some(token.text_range());
+                                        break 'outer;
+                                    }
+                                }
+                            }
+
+                            (
+                                ast_id.file_id,
+                                SyntaxNodePtr::from(AstPtr::new(&node)),
+                                Some(derive_name.clone()),
+                            )
+                        }
+                    };
+                    sink.push(UnresolvedProcMacro {
+                        file,
+                        node: ast,
+                        precise_location,
+                        macro_name: name,
+                    });
+                }
+
+                DiagnosticKind::UnresolvedMacroCall { ast, path } => {
+                    let node = ast.to_node(db.upcast());
+                    sink.push(UnresolvedMacroCall {
+                        file: ast.file_id,
+                        node: AstPtr::new(&node),
+                        path: path.clone(),
+                    });
+                }
+
+                DiagnosticKind::MacroError { ast, message } => {
+                    let (file, ast) = match ast {
+                        MacroCallKind::FnLike { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
+                        }
+                        MacroCallKind::Derive { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
+                        }
+                    };
+                    sink.push(MacroError { file, node: ast, message: message.clone() });
+                }
+            }
+        }
+    }
+}
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 3ea4729080..2ae740d0e6 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -33,10 +33,7 @@ use crate::{
     },
     macro_call_as_call_id,
     nameres::{
-        diagnostics::DefDiagnostic,
-        mod_resolution::ModDir,
-        path_resolution::ReachedFixedPoint,
-        proc_macro::{ProcMacroDef, ProcMacroKind},
+        diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint,
         BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
     },
     path::{ImportAlias, ModPath, PathKind},
@@ -47,6 +44,8 @@ use crate::{
     UnresolvedMacro,
 };
 
+use super::proc_macro::{ProcMacroDef, ProcMacroKind};
+
 const GLOB_RECURSION_LIMIT: usize = 100;
 const EXPANSION_DEPTH_LIMIT: usize = 128;
 const FIXED_POINT_LIMIT: usize = 8192;
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
deleted file mode 100644
index 8f2f0ff9f5..0000000000
--- a/crates/hir_def/src/nameres/diagnostics.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-//! Diagnostics emitted during DefMap construction.
-
-use cfg::{CfgExpr, CfgOptions};
-use hir_expand::MacroCallKind;
-use syntax::ast;
-
-use crate::{nameres::LocalModuleId, path::ModPath, AstId};
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum DefDiagnosticKind {
-    UnresolvedModule { ast: AstId<ast::Module>, candidate: String },
-
-    UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
-
-    UnresolvedImport { ast: AstId<ast::Use>, index: usize },
-
-    UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
-
-    UnresolvedProcMacro { ast: MacroCallKind },
-
-    UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
-
-    MacroError { ast: MacroCallKind, message: String },
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct DefDiagnostic {
-    pub in_module: LocalModuleId,
-    pub kind: DefDiagnosticKind,
-}
-
-impl DefDiagnostic {
-    pub(super) fn unresolved_module(
-        container: LocalModuleId,
-        declaration: AstId<ast::Module>,
-        candidate: String,
-    ) -> Self {
-        Self {
-            in_module: container,
-            kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate },
-        }
-    }
-
-    pub(super) fn unresolved_extern_crate(
-        container: LocalModuleId,
-        declaration: AstId<ast::ExternCrate>,
-    ) -> Self {
-        Self {
-            in_module: container,
-            kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
-        }
-    }
-
-    pub(super) fn unresolved_import(
-        container: LocalModuleId,
-        ast: AstId<ast::Use>,
-        index: usize,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
-    }
-
-    pub(super) fn unconfigured_code(
-        container: LocalModuleId,
-        ast: AstId<ast::Item>,
-        cfg: CfgExpr,
-        opts: CfgOptions,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
-    }
-
-    pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast } }
-    }
-
-    pub(super) fn macro_error(
-        container: LocalModuleId,
-        ast: MacroCallKind,
-        message: String,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
-    }
-
-    pub(super) fn unresolved_macro_call(
-        container: LocalModuleId,
-        ast: AstId<ast::MacroCall>,
-        path: ModPath,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
-    }
-}
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index d9ec03d2da..45ab9d0ffb 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -71,7 +71,7 @@ impl ModPath {
     }
 
     /// Calls `cb` with all paths, represented by this use item.
-    pub fn expand_use_item(
+    pub(crate) fn expand_use_item(
         db: &dyn DefDatabase,
         item_src: InFile<ast::Use>,
         hygiene: &Hygiene,
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 54ede73939..5d5dd9c8f8 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -205,14 +205,15 @@ fn lower_generic_args_from_fn_path(
 ) -> Option<GenericArgs> {
     let mut args = Vec::new();
     let mut bindings = Vec::new();
-    let params = params?;
-    let mut param_types = Vec::new();
-    for param in params.params() {
-        let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
-        param_types.push(type_ref);
+    if let Some(params) = params {
+        let mut param_types = Vec::new();
+        for param in params.params() {
+            let type_ref = TypeRef::from_ast_opt(&ctx, param.ty());
+            param_types.push(type_ref);
+        }
+        let arg = GenericArg::Type(TypeRef::Tuple(param_types));
+        args.push(arg);
     }
-    let arg = GenericArg::Type(TypeRef::Tuple(param_types));
-    args.push(arg);
     if let Some(ret_type) = ret_type {
         let type_ref = TypeRef::from_ast_opt(&ctx, ret_type.ty());
         bindings.push(AssociatedTypeBinding {
@@ -220,14 +221,10 @@ fn lower_generic_args_from_fn_path(
             type_ref: Some(type_ref),
             bounds: Vec::new(),
         });
+    }
+    if args.is_empty() && bindings.is_empty() {
+        None
     } else {
-        // -> ()
-        let type_ref = TypeRef::Tuple(Vec::new());
-        bindings.push(AssociatedTypeBinding {
-            name: name![Output],
-            type_ref: Some(type_ref),
-            bounds: Vec::new(),
-        });
+        Some(GenericArgs { args, has_self_type: false, bindings })
     }
-    Some(GenericArgs { args, has_self_type: false, bindings })
 }
diff --git a/crates/hir_def/src/resolver.rs b/crates/hir_def/src/resolver.rs
index fb8a6f2606..0391cc49b6 100644
--- a/crates/hir_def/src/resolver.rs
+++ b/crates/hir_def/src/resolver.rs
@@ -337,34 +337,22 @@ impl Resolver {
     pub fn traits_in_scope(&self, db: &dyn DefDatabase) -> FxHashSet<TraitId> {
         let mut traits = FxHashSet::default();
         for scope in &self.scopes {
-            match scope {
-                Scope::ModuleScope(m) => {
-                    if let Some(prelude) = m.def_map.prelude() {
+            if let Scope::ModuleScope(m) = scope {
+                if let Some(prelude) = m.def_map.prelude() {
+                    let prelude_def_map = prelude.def_map(db);
+                    traits.extend(prelude_def_map[prelude.local_id].scope.traits());
+                }
+                traits.extend(m.def_map[m.module_id].scope.traits());
+
+                // Add all traits that are in scope because of the containing DefMaps
+                m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
+                    if let Some(prelude) = def_map.prelude() {
                         let prelude_def_map = prelude.def_map(db);
                         traits.extend(prelude_def_map[prelude.local_id].scope.traits());
                     }
-                    traits.extend(m.def_map[m.module_id].scope.traits());
-
-                    // Add all traits that are in scope because of the containing DefMaps
-                    m.def_map.with_ancestor_maps(db, m.module_id, &mut |def_map, module| {
-                        if let Some(prelude) = def_map.prelude() {
-                            let prelude_def_map = prelude.def_map(db);
-                            traits.extend(prelude_def_map[prelude.local_id].scope.traits());
-                        }
-                        traits.extend(def_map[module].scope.traits());
-                        None::<()>
-                    });
-                }
-                &Scope::ImplDefScope(impl_) => {
-                    if let Some(target_trait) = &db.impl_data(impl_).target_trait {
-                        if let Some(TypeNs::TraitId(trait_)) =
-                            self.resolve_path_in_type_ns_fully(db, target_trait.path.mod_path())
-                        {
-                            traits.insert(trait_);
-                        }
-                    }
-                }
-                _ => (),
+                    traits.extend(def_map[module].scope.traits());
+                    None::<()>
+                });
             }
         }
         traits
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index e8f4af3095..625c26f0a0 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -186,7 +186,7 @@ fn parse_macro_expansion(
         // The final goal we would like to make all parse_macro success,
         // such that the following log will not call anyway.
         let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
-        let node = loc.kind.to_node(db);
+        let node = loc.kind.node(db);
 
         // collect parent information for warning log
         let parents =
diff --git a/crates/hir_ty/src/diagnostics_sink.rs b/crates/hir_expand/src/diagnostics.rs
similarity index 99%
rename from crates/hir_ty/src/diagnostics_sink.rs
rename to crates/hir_expand/src/diagnostics.rs
index 084fa8b06a..bf0b85ce9d 100644
--- a/crates/hir_ty/src/diagnostics_sink.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -16,9 +16,10 @@
 
 use std::{any::Any, fmt};
 
-use hir_expand::InFile;
 use syntax::SyntaxNodePtr;
 
+use crate::InFile;
+
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub struct DiagnosticCode(pub &'static str);
 
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 10d37234e7..6be4516a32 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -8,6 +8,7 @@ pub mod db;
 pub mod ast_id_map;
 pub mod name;
 pub mod hygiene;
+pub mod diagnostics;
 pub mod builtin_derive;
 pub mod builtin_macro;
 pub mod proc_macro;
@@ -107,7 +108,7 @@ impl HirFileId {
             HirFileIdRepr::FileId(_) => None,
             HirFileIdRepr::MacroFile(macro_file) => {
                 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
-                Some(loc.kind.to_node(db))
+                Some(loc.kind.node(db))
             }
         }
     }
@@ -152,7 +153,7 @@ impl HirFileId {
             HirFileIdRepr::MacroFile(macro_file) => {
                 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
                 let item = match loc.def.kind {
-                    MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
+                    MacroDefKind::BuiltInDerive(..) => loc.kind.node(db),
                     _ => return None,
                 };
                 Some(item.with_value(ast::Item::cast(item.value.clone())?))
@@ -268,7 +269,7 @@ impl MacroCallKind {
         }
     }
 
-    pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
+    fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
         match self {
             MacroCallKind::FnLike { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 7598e2193a..84fc8ce14d 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -8,14 +8,12 @@ use std::{any::Any, fmt};
 
 use base_db::CrateId;
 use hir_def::{DefWithBodyId, ModuleDefId};
+use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
 use hir_expand::{name::Name, HirFileId, InFile};
 use stdx::format_to;
 use syntax::{ast, AstPtr, SyntaxNodePtr};
 
-use crate::{
-    db::HirDatabase,
-    diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
-};
+use crate::db::HirDatabase;
 
 pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
 
@@ -448,13 +446,15 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap {
 mod tests {
     use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
     use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
-    use hir_expand::db::AstDatabase;
+    use hir_expand::{
+        db::AstDatabase,
+        diagnostics::{Diagnostic, DiagnosticSinkBuilder},
+    };
     use rustc_hash::FxHashMap;
     use syntax::{TextRange, TextSize};
 
     use crate::{
         diagnostics::{validate_body, validate_module_item},
-        diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder},
         test_db::TestDB,
     };
 
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
index ef982cbcd4..075dc4131f 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -19,7 +19,10 @@ use hir_def::{
     src::HasSource,
     AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
 };
-use hir_expand::name::{AsName, Name};
+use hir_expand::{
+    diagnostics::DiagnosticSink,
+    name::{AsName, Name},
+};
 use stdx::{always, never};
 use syntax::{
     ast::{self, NameOwner},
@@ -29,7 +32,6 @@ use syntax::{
 use crate::{
     db::HirDatabase,
     diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
-    diagnostics_sink::DiagnosticSink,
 };
 
 mod allow {
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 86f82e3fa4..d1f113e7ff 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -5,7 +5,7 @@
 use std::sync::Arc;
 
 use hir_def::{expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId};
-use hir_expand::name;
+use hir_expand::{diagnostics::DiagnosticSink, name};
 use rustc_hash::FxHashSet;
 use syntax::{ast, AstPtr};
 
@@ -16,7 +16,6 @@ use crate::{
         MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
         MissingPatFields, RemoveThisSemicolon,
     },
-    diagnostics_sink::DiagnosticSink,
     AdtId, InferenceResult, Interner, TyExt, TyKind,
 };
 
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index c3c483425e..5d13bddea3 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -9,10 +9,10 @@ use hir_def::{
     resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
     DefWithBodyId,
 };
+use hir_expand::diagnostics::DiagnosticSink;
 
 use crate::{
-    db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
-    Interner, TyExt, TyKind,
+    db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Interner, TyExt, TyKind,
 };
 
 pub(super) struct UnsafeValidator<'a, 'b: 'a> {
diff --git a/crates/hir_ty/src/display.rs b/crates/hir_ty/src/display.rs
index 44f843bf38..637bbc634d 100644
--- a/crates/hir_ty/src/display.rs
+++ b/crates/hir_ty/src/display.rs
@@ -778,10 +778,8 @@ fn write_bounds_like_dyn_trait(
             }
             WhereClause::AliasEq(alias_eq) if is_fn_trait => {
                 is_fn_trait = false;
-                if !alias_eq.ty.is_unit() {
-                    write!(f, " -> ")?;
-                    alias_eq.ty.hir_fmt(f)?;
-                }
+                write!(f, " -> ")?;
+                alias_eq.ty.hir_fmt(f)?;
             }
             WhereClause::AliasEq(AliasEq { ty, alias }) => {
                 // in types in actual Rust, these will always come
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 8cefd80f3a..db3c937ff5 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -28,14 +28,13 @@ use hir_def::{
     AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup,
     TraitId, TypeAliasId, VariantId,
 };
-use hir_expand::name::name;
+use hir_expand::{diagnostics::DiagnosticSink, name::name};
 use la_arena::ArenaMap;
 use rustc_hash::FxHashMap;
 use stdx::impl_from;
 use syntax::SmolStr;
 
 use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
-use crate::diagnostics_sink::DiagnosticSink;
 use crate::{
     db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic,
     lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution,
@@ -579,14 +578,9 @@ impl<'a> InferenceContext<'a> {
     }
 
     fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
-        // FIXME resolve via lang_item once try v2 is stable
         let path = path![core::ops::Try];
         let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?;
-        let trait_data = self.db.trait_data(trait_);
-        trait_data
-            // FIXME remove once try v2 is stable
-            .associated_type_by_name(&name![Ok])
-            .or_else(|| trait_data.associated_type_by_name(&name![Output]))
+        self.db.trait_data(trait_).associated_type_by_name(&name![Ok])
     }
 
     fn resolve_ops_neg_output(&self) -> Option<TypeAliasId> {
@@ -799,11 +793,11 @@ impl std::ops::BitOrAssign for Diverges {
 
 mod diagnostics {
     use hir_def::{expr::ExprId, DefWithBodyId};
+    use hir_expand::diagnostics::DiagnosticSink;
 
     use crate::{
         db::HirDatabase,
         diagnostics::{BreakOutsideOfLoop, NoSuchField},
-        diagnostics_sink::DiagnosticSink,
     };
 
     #[derive(Debug, PartialEq, Eq, Clone)]
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/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs
index 50e0d63333..ef021978ad 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -21,7 +21,6 @@ mod utils;
 mod walk;
 pub mod db;
 pub mod diagnostics;
-pub mod diagnostics_sink;
 pub mod display;
 pub mod method_resolution;
 pub mod primitive;
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dcac7c76d2..27d347dbd9 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -299,10 +299,10 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
 
 #[cfg(test)]
 mod tests {
-    use expect_test::Expect;
+    use expect_test::{expect, Expect};
     use ide_assists::AssistResolveStrategy;
     use stdx::trim_indent;
-    use test_utils::{assert_eq_text, extract_annotations};
+    use test_utils::assert_eq_text;
 
     use crate::{fixture, DiagnosticsConfig};
 
@@ -396,51 +396,26 @@ mod tests {
         expect.assert_debug_eq(&diagnostics)
     }
 
-    pub(crate) fn check_diagnostics(ra_fixture: &str) {
-        let (analysis, file_id) = fixture::file(ra_fixture);
-        let diagnostics = analysis
-            .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
-            .unwrap();
-
-        let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
-        let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>();
-        assert_eq!(expected, actual);
-    }
-
     #[test]
     fn test_unresolved_macro_range() {
-        check_diagnostics(
-            r#"
-foo::bar!(92);
-   //^^^ unresolved macro `foo::bar!`
-"#,
-        );
-    }
-
-    #[test]
-    fn unresolved_import_in_use_tree() {
-        // Only the relevant part of a nested `use` item should be highlighted.
-        check_diagnostics(
-            r#"
-use does_exist::{Exists, DoesntExist};
-                       //^^^^^^^^^^^ unresolved import
-
-use {does_not_exist::*, does_exist};
-   //^^^^^^^^^^^^^^^^^ unresolved import
-
-use does_not_exist::{
-    a,
-  //^ unresolved import
-    b,
-  //^ unresolved import
-    c,
-  //^ unresolved import
-};
-
-mod does_exist {
-    pub struct Exists;
-}
-"#,
+        check_expect(
+            r#"foo::bar!(92);"#,
+            expect![[r#"
+                [
+                    Diagnostic {
+                        message: "unresolved macro `foo::bar!`",
+                        range: 5..8,
+                        severity: Error,
+                        fixes: None,
+                        unused: false,
+                        code: Some(
+                            DiagnosticCode(
+                                "unresolved-macro-call",
+                            ),
+                        ),
+                    },
+                ]
+            "#]],
         );
     }
 
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index 43356a94ed..05130a2376 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -1,4 +1,4 @@
-use hir::{AsAssocItem, Impl, Semantics};
+use hir::{Impl, Semantics};
 use ide_db::{
     defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -36,7 +36,6 @@ pub(crate) fn goto_implementation(
         }
         ast::NameLike::Lifetime(_) => None,
     }?;
-
     let def = match def {
         Definition::ModuleDef(def) => def,
         _ => return None,
@@ -49,18 +48,6 @@ 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_item(&sema, trait_, name)
-        }
-        hir::ModuleDef::Const(c) => {
-            let assoc = c.as_assoc_item(sema.db)?;
-            let name = assoc.name(sema.db)?;
-            let trait_ = assoc.containing_trait(sema.db)?;
-            impls_for_trait_item(&sema, trait_, name)
-        }
         _ => return None,
     };
     Some(RangeInfo { range: node.syntax().text_range(), info: navs })
@@ -77,23 +64,6 @@ fn impls_for_trait(sema: &Semantics<RootDatabase>, trait_: hir::Trait) -> Vec<Na
         .collect()
 }
 
-fn impls_for_trait_item(
-    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;
@@ -289,44 +259,6 @@ 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!");
-    }
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn goto_implementation_trait_assoc_const() {
-        check(
-            r#"
-trait Tr {
-    const C$0: usize;
-}
-
-struct S;
-
-impl Tr for S {
-    const C: usize = 4;
-        //^
-}
 "#,
         );
     }
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 004d9cb688..f3284bb96a 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -1,4 +1,3 @@
-use ide_db::base_db::Upcast;
 use ide_db::RootDatabase;
 use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
 
@@ -32,7 +31,6 @@ pub(crate) fn goto_type_definition(
                 ast::Pat(it) => sema.type_of_pat(&it)?,
                 ast::SelfParam(it) => sema.type_of_self(&it)?,
                 ast::Type(it) => sema.resolve_type(&it)?,
-                ast::RecordField(it) => sema.to_def(&it).map(|d| d.ty(db.upcast()))?,
                 _ => return None,
             }
         };
@@ -163,34 +161,4 @@ impl Foo$0 {}
 "#,
         )
     }
-
-    #[test]
-    fn goto_def_for_struct_field() {
-        check(
-            r#"
-struct Bar;
-     //^^^
-
-struct Foo {
-    bar$0: Bar,
-}
-"#,
-        );
-    }
-
-    #[test]
-    fn goto_def_for_enum_struct_field() {
-        check(
-            r#"
-struct Bar;
-     //^^^
-
-enum Foo {
-    Bar {
-        bar$0: Bar
-    },
-}
-"#,
-        );
-    }
 }
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index b4a3d39c91..9503c936d1 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -71,7 +71,7 @@ pub(super) fn element(
         }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
-            highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
+            highlight_func_by_name_ref(sema, krate, &name_ref).unwrap_or_else(|| {
                 let is_self = name_ref.self_token().is_some();
                 let h = match NameRefClass::classify(sema, &name_ref) {
                     Some(name_kind) => match name_kind {
@@ -108,7 +108,7 @@ pub(super) fn element(
                         NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
                     },
                     None if syntactic_name_ref_highlighting => {
-                        highlight_name_ref_by_syntax(name_ref, sema)
+                        highlight_name_ref_by_syntax(name_ref, sema, krate)
                     }
                     None => HlTag::UnresolvedReference.into(),
                 };
@@ -434,19 +434,23 @@ fn highlight_def(db: &RootDatabase, krate: Option<hir::Crate>, def: Definition)
 
 fn highlight_func_by_name_ref(
     sema: &Semantics<RootDatabase>,
+    krate: Option<hir::Crate>,
     name_ref: &ast::NameRef,
 ) -> Option<Highlight> {
     let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
-    highlight_method_call(sema, &mc)
+    highlight_method_call(sema, krate, &mc)
 }
 
 fn highlight_method_call(
     sema: &Semantics<RootDatabase>,
+    krate: Option<hir::Crate>,
     method_call: &ast::MethodCallExpr,
 ) -> Option<Highlight> {
     let func = sema.resolve_method_call(&method_call)?;
+
     let mut h = SymbolKind::Function.into();
     h |= HlMod::Associated;
+
     if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
         h |= HlMod::Unsafe;
     }
@@ -454,7 +458,10 @@ fn highlight_method_call(
         h |= HlMod::Async;
     }
     if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() {
-        h |= HlMod::Trait
+        h |= HlMod::Trait;
+    }
+    if Some(func.module(sema.db).krate()) != krate {
+        h |= HlMod::Library;
     }
 
     if let Some(self_param) = func.self_param(sema.db) {
@@ -503,7 +510,11 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
     tag.into()
 }
 
-fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
+fn highlight_name_ref_by_syntax(
+    name: ast::NameRef,
+    sema: &Semantics<RootDatabase>,
+    krate: Option<hir::Crate>,
+) -> Highlight {
     let default = HlTag::UnresolvedReference;
 
     let parent = match name.syntax().parent() {
@@ -514,7 +525,7 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
     match parent.kind() {
         METHOD_CALL_EXPR => {
             return ast::MethodCallExpr::cast(parent)
-                .and_then(|it| highlight_method_call(sema, &it))
+                .and_then(|it| highlight_method_call(sema, krate, &it))
                 .unwrap_or_else(|| SymbolKind::Function.into());
         }
         FIELD_EXPR => {
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs
index 40006fb74f..2f95b86874 100644
--- a/crates/ide_completion/src/completions/record.rs
+++ b/crates/ide_completion/src/completions/record.rs
@@ -108,6 +108,8 @@ fn process(f: S) {
         check_snippet(
             test_code,
             expect![[r#"
+                sn pd
+                sn ppd
                 fd ..Default::default()
             "#]],
         );
@@ -177,7 +179,13 @@ fn process(f: S) {
             "#]],
         );
 
-        check_snippet(test_code, expect![[r#""#]]);
+        check_snippet(
+            test_code,
+            expect![[r#"
+                sn pd
+                sn ppd
+            "#]],
+        );
     }
 
     #[test]
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index 14cfb61dee..7f7830976b 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -22,10 +22,8 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
         None => return,
     };
 
-    if ctx.can_be_stmt {
-        snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
-        snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
-    }
+    snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
+    snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
 }
 
 pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
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/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 93b5ff55f7..7a53e4a8b9 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -455,11 +455,7 @@ impl ProjectFolders {
                 dirs.include.extend(root.include);
                 dirs.exclude.extend(root.exclude);
                 for excl in global_excludes {
-                    if dirs
-                        .include
-                        .iter()
-                        .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
-                    {
+                    if dirs.include.iter().any(|incl| incl.starts_with(excl)) {
                         dirs.exclude.push(excl.clone());
                     }
                 }
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_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index d4fae05a69..3e8f16306b 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -96,26 +96,26 @@ fn f() {
     // The three g̶e̶n̶d̶e̶r̶s̶ statements:
 
     #[cfg(a)] fn f() {}  // Item statement
-  //^^^^^^^^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     #[cfg(a)] {}         // Expression statement
-  //^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     #[cfg(a)] let x = 0; // let statement
-  //^^^^^^^^^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
 
     abc(#[cfg(a)] 0);
-      //^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     let x = Struct {
         #[cfg(a)] f: 0,
-      //^^^^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     };
     match () {
         () => (),
         #[cfg(a)] () => (),
-      //^^^^^^^^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     }
 
     #[cfg(a)] 0          // Trailing expression of block
-  //^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
 }
     ",
     );
@@ -188,7 +188,7 @@ fn unresolved_macro_diag() {
         r#"
 fn f() {
     m!();
-  //^^^^ UnresolvedMacroCall
+  //^^^^ unresolved macro `m!`
 }
       "#,
     );
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index ec66709528..75147d973a 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -18,13 +18,40 @@ fn unresolved_import() {
         r"
         use does_exist;
         use does_not_exist;
-      //^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+          //^^^^^^^^^^^^^^ unresolved import
 
         mod does_exist {}
         ",
     );
 }
 
+#[test]
+fn unresolved_import_in_use_tree() {
+    // Only the relevant part of a nested `use` item should be highlighted.
+    check_diagnostics(
+        r"
+        use does_exist::{Exists, DoesntExist};
+                               //^^^^^^^^^^^ unresolved import
+
+        use {does_not_exist::*, does_exist};
+           //^^^^^^^^^^^^^^^^^ unresolved import
+
+        use does_not_exist::{
+            a,
+          //^ unresolved import
+            b,
+          //^ unresolved import
+            c,
+          //^ unresolved import
+        };
+
+        mod does_exist {
+            pub struct Exists;
+        }
+        ",
+    );
+}
+
 #[test]
 fn unresolved_extern_crate() {
     check_diagnostics(
@@ -32,7 +59,7 @@ fn unresolved_extern_crate() {
         //- /main.rs crate:main deps:core
         extern crate core;
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
         //- /lib.rs crate:core
         ",
     );
@@ -45,7 +72,7 @@ fn extern_crate_self_as() {
         r"
         //- /lib.rs
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
         // Should not error.
         extern crate self as foo;
         struct Foo;
@@ -61,18 +88,18 @@ fn dedup_unresolved_import_from_unresolved_crate() {
         //- /main.rs crate:main
         mod a {
             extern crate doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+          //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
 
             // Should not error, since we already errored for the missing crate.
             use doesnotexist::{self, bla, *};
 
             use crate::doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+              //^^^^^^^^^^^^^^^^^^^ unresolved import
         }
 
         mod m {
             use super::doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+              //^^^^^^^^^^^^^^^^^^^ unresolved import
         }
         ",
     );
@@ -85,7 +112,7 @@ fn unresolved_module() {
         //- /lib.rs
         mod foo;
           mod bar;
-        //^^^^^^^^ UnresolvedModule
+        //^^^^^^^^ unresolved module
         mod baz {}
         //- /foo.rs
         ",
@@ -100,16 +127,16 @@ fn inactive_item() {
         r#"
         //- /lib.rs
           #[cfg(no)] pub fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
 
           #[cfg(no)] #[cfg(no2)] mod m;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
 
           #[cfg(all(not(a), b))] enum E {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
 
           #[cfg(feature = "std")] use std;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
         "#,
     );
 }
@@ -122,14 +149,14 @@ fn inactive_via_cfg_attr() {
         r#"
         //- /lib.rs
           #[cfg_attr(not(never), cfg(no))] fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
 
           #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
 
           #[cfg_attr(never, cfg(no))] fn g() {}
 
           #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
         "#,
     );
 }
@@ -143,7 +170,7 @@ fn unresolved_legacy_scope_macro() {
 
           m!();
           m2!();
-        //^^^^^^ UnresolvedMacroCall
+        //^^^^^^ unresolved macro `self::m2!`
         "#,
     );
 }
@@ -160,7 +187,7 @@ fn unresolved_module_scope_macro() {
 
           self::m!();
           self::m2!();
-        //^^^^^^^^^^^^ UnresolvedMacroCall
+        //^^^^^^^^^^^^ unresolved macro `self::m2!`
         "#,
     );
 }
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index 6c357c915b..8fa703a574 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -5,20 +5,19 @@ use std::{
     sync::{Arc, Mutex},
 };
 
-use base_db::{
-    salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
-};
+use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast};
 use base_db::{AnchoredPath, SourceDatabase};
+use hir_expand::diagnostics::Diagnostic;
+use hir_expand::diagnostics::DiagnosticSinkBuilder;
 use hir_expand::{db::AstDatabase, InFile};
 use rustc_hash::FxHashMap;
 use rustc_hash::FxHashSet;
-use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
+use syntax::{algo, ast, AstNode, TextRange, TextSize};
 use test_utils::extract_annotations;
 
 use crate::{
-    body::BodyDiagnostic,
     db::DefDatabase,
-    nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
+    nameres::{DefMap, ModuleSource},
     src::HasSource,
     LocalModuleId, Lookup, ModuleDefId, ModuleId,
 };
@@ -263,70 +262,19 @@ impl TestDB {
             .collect()
     }
 
-    pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
+    pub(crate) fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
         let crate_graph = self.crate_graph();
         for krate in crate_graph.iter() {
             let crate_def_map = self.crate_def_map(krate);
 
-            for diag in crate_def_map.diagnostics() {
-                let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
-                    DefDiagnosticKind::UnresolvedModule { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
-                    }
-                    DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
-                    }
-                    DefDiagnosticKind::UnresolvedImport { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
-                    }
-                    DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
-                    }
-                    DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
-                        (ast.to_node(self.upcast()), "UnresolvedProcMacro")
-                    }
-                    DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
-                    }
-                    DefDiagnosticKind::MacroError { ast, message } => {
-                        (ast.to_node(self.upcast()), message.as_str())
-                    }
-                };
-
-                let frange = node.as_ref().original_file_range(self);
-                cb(frange, message.to_string())
-            }
+            let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
+            for (module_id, module) in crate_def_map.modules() {
+                crate_def_map.add_diagnostics(self, module_id, &mut sink);
 
-            for (_module_id, module) in crate_def_map.modules() {
                 for decl in module.scope.declarations() {
                     if let ModuleDefId::FunctionId(it) = decl {
                         let source_map = self.body_with_source_map(it.into()).1;
-                        for diag in source_map.diagnostics() {
-                            let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
-                                BodyDiagnostic::InactiveCode { node, .. } => {
-                                    (node.clone().map(|it| it.into()), "InactiveCode")
-                                }
-                                BodyDiagnostic::MacroError { node, message } => {
-                                    (node.clone().map(|it| it.into()), message.as_str())
-                                }
-                                BodyDiagnostic::UnresolvedProcMacro { node } => {
-                                    (node.clone().map(|it| it.into()), "UnresolvedProcMacro")
-                                }
-                                BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
-                                    (node.clone().map(|it| it.into()), "UnresolvedMacroCall")
-                                }
-                            };
-
-                            let root = self.parse_or_expand(ptr.file_id).unwrap();
-                            let node = ptr.map(|ptr| ptr.to_node(&root));
-                            let frange = node.as_ref().original_file_range(self);
-                            cb(frange, message.to_string())
-                        }
+                        source_map.add_diagnostics(self, &mut sink);
                     }
                 }
             }
@@ -339,7 +287,14 @@ impl TestDB {
         assert!(!annotations.is_empty());
 
         let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
-        db.diagnostics(&mut |frange, message| {
+        db.diagnostics(|d| {
+            let src = d.display_source();
+            let root = db.parse_or_expand(src.file_id).unwrap();
+
+            let node = src.map(|ptr| ptr.to_node(&root));
+            let frange = node.as_ref().original_file_range(db);
+
+            let message = d.message();
             actual.entry(frange.file_id).or_default().push((frange.range, message));
         });
 
@@ -364,7 +319,7 @@ impl TestDB {
         assert!(annotations.is_empty());
 
         let mut has_diagnostics = false;
-        db.diagnostics(&mut |_, _| {
+        db.diagnostics(|_| {
             has_diagnostics = true;
         });
 
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!() }': ()
-        "#]],
-    );
-}
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index 6ad96bfe38..a5a2df54cb 100644
--- a/crates/hir_ty/src/tests/traits.rs
+++ b/crates/hir_ty/src/tests/traits.rs
@@ -160,43 +160,6 @@ mod result {
     );
 }
 
-#[test]
-fn infer_tryv2() {
-    check_types(
-        r#"
-//- /main.rs crate:main deps:core
-fn test() {
-    let r: Result<i32, u64> = Result::Ok(1);
-    let v = r?;
-    v;
-} //^ i32
-
-//- /core.rs crate:core
-#[prelude_import] use ops::*;
-mod ops {
-    trait Try {
-        type Output;
-        type Residual;
-    }
-}
-
-#[prelude_import] use result::*;
-mod result {
-    enum Infallible {}
-    enum Result<O, E> {
-        Ok(O),
-        Err(E)
-    }
-
-    impl<O, E> crate::ops::Try for Result<O, E> {
-        type Output = O;
-        type Error = Result<Infallible, E>;
-    }
-}
-"#,
-    );
-}
-
 #[test]
 fn infer_for_loop() {
     check_types(
@@ -3078,7 +3041,7 @@ fn infer_fn_trait_arg() {
 
 #[test]
 fn infer_box_fn_arg() {
-    // The type mismatch is because we don't define Unsize and CoerceUnsized
+    // The type mismatch is a bug
     check_infer_with_mismatches(
         r#"
 //- /lib.rs deps:std
@@ -3138,7 +3101,7 @@ fn foo() {
             555..557 'ps': {unknown}
             559..561 '{}': ()
             568..569 'f': Box<dyn FnOnce(&Option<i32>)>
-            568..573 'f(&s)': ()
+            568..573 'f(&s)': FnOnce::Output<dyn FnOnce(&Option<i32>), (&Option<i32>,)>
             570..572 '&s': &Option<i32>
             571..572 's': Option<i32>
             549..562: expected Box<dyn FnOnce(&Option<i32>)>, got Box<|{unknown}| -> ()>
@@ -3608,55 +3571,3 @@ fn main() {
         "#]],
     )
 }
-
-#[test]
-fn fn_returning_unit() {
-    check_infer_with_mismatches(
-        r#"
-#[lang = "fn_once"]
-trait FnOnce<Args> {
-    type Output;
-}
-
-fn test<F: FnOnce()>(f: F) {
-    let _: () = f();
-}"#,
-        expect![[r#"
-            82..83 'f': F
-            88..112 '{     ...f(); }': ()
-            98..99 '_': ()
-            106..107 'f': F
-            106..109 'f()': ()
-        "#]],
-    );
-}
-
-#[test]
-fn trait_in_scope_of_trait_impl() {
-    check_infer(
-        r#"
-mod foo {
-    pub trait Foo {
-        fn foo(self);
-        fn bar(self) -> usize { 0 }
-    }
-}
-impl foo::Foo for u32 {
-    fn foo(self) {
-        let _x = self.bar();
-    }
-}
-    "#,
-        expect![[r#"
-            45..49 'self': Self
-            67..71 'self': Self
-            82..87 '{ 0 }': usize
-            84..85 '0': usize
-            131..135 'self': u32
-            137..173 '{     ...     }': ()
-            151..153 '_x': usize
-            156..160 'self': u32
-            156..166 'self.bar()': usize
-        "#]],
-    );
-}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 055d211090..0264e39a33 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -258,7 +258,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
     <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
-    <span class="keyword control">if</span> <span class="keyword">let</span> <span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Die</span> <span class="operator">=</span> <span class="variable">control_flow</span> <span class="brace">{</span>
+    <span class="keyword control">if</span> <span class="variable">control_flow</span><span class="operator">.</span><span class="function associated consuming library">should_die</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
         foo::<span class="macro">die!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="brace">}</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index be4447ebbf..662b534813 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -232,7 +232,7 @@ fn use_foo_items() {
 
     let control_flow = foo::identity(foo::ControlFlow::Continue);
 
-    if let foo::ControlFlow::Die = control_flow {
+    if control_flow.should_die() {
         foo::die!();
     }
 }
@@ -249,6 +249,12 @@ pub enum ControlFlow {
     Die,
 }
 
+impl ControlFlow {
+    pub fn should_die(self) -> bool {
+        matches!(self, ControlFlow::Die)
+    }
+}
+
 pub fn identity<T>(x: T) -> T { x }
 
 pub mod consts {