Repository: rust-analyzer/rust-analyzer
Pull request #8973 (2021-05-24 16:52:18)

internal: move diagnostics to hir


Patch:
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 414c3f35ee..22ec7c6aca 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -3,13 +3,227 @@
 //!
 //! This probably isn't the best way to do this -- ideally, diagnistics should
 //! be expressed in terms of hir types themselves.
-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,
+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},
 };
+
+// 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 cdf65a0445..1ecd2391b0 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -35,12 +35,18 @@ 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 _,
@@ -50,11 +56,12 @@ use hir_def::{
     LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
     TypeParamId, UnionId,
 };
-use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
+use hir_expand::{name::name, MacroCallKind, MacroDefKind};
 use hir_ty::{
     autoderef,
     consteval::ConstExt,
     could_unify,
+    diagnostics_sink::DiagnosticSink,
     method_resolution::{self, def_crates, TyFingerprint},
     primitive::UintTy,
     subst_prefix,
@@ -65,11 +72,12 @@ 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, SmolStr,
+    AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr,
 };
 use tt::{Ident, Leaf, Literal, TokenTree};
 
@@ -442,7 +450,137 @@ impl Module {
             format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
         });
         let def_map = self.id.def_map(db.upcast());
-        def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
+        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() });
+                }
+            }
+        }
         for decl in self.declarations(db) {
             match decl {
                 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
@@ -865,7 +1003,37 @@ impl Function {
 
     pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
         let krate = self.module(db).id.krate();
-        hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
+
+        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_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 98b485b602..c521879c87 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,7 +1,6 @@
 //! Defines `Body`: a lowered representation of bodies of functions, statics and
 //! consts.
 mod lower;
-mod diagnostics;
 #[cfg(test)]
 mod tests;
 pub mod scope;
@@ -9,17 +8,16 @@ pub mod scope;
 use std::{mem, ops::Index, sync::Arc};
 
 use base_db::CrateId;
-use cfg::CfgOptions;
+use cfg::{CfgExpr, CfgOptions};
 use drop_bomb::DropBomb;
 use either::Either;
 use hir_expand::{
-    ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult,
-    HirFileId, InFile, MacroDefId,
+    ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
 };
 use la_arena::{Arena, ArenaMap};
 use profile::Count;
 use rustc_hash::FxHashMap;
-use syntax::{ast, AstNode, AstPtr};
+use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
 
 use crate::{
     attr::{Attrs, RawAttrs},
@@ -273,12 +271,20 @@ 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<diagnostics::BodyDiagnostic>,
+    diagnostics: Vec<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,
@@ -416,9 +422,8 @@ impl BodySourceMap {
         self.field_map.get(&src).cloned()
     }
 
-    pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
-        for diag in &self.diagnostics {
-            diag.add_to(sink);
-        }
+    /// Get a reference to the body source map's diagnostics.
+    pub fn diagnostics(&self) -> &[BodyDiagnostic] {
+        &self.diagnostics
     }
 }
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
deleted file mode 100644
index f6992c9a8a..0000000000
--- a/crates/hir_def/src/body/diagnostics.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-//! 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 2a7e0205f5..da1fdac33a 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,
+    ExpandError, HirFileId, InFile,
 };
 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,8 +38,6 @@ use crate::{
     AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
 };
 
-use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
-
 pub struct LowerCtx<'a> {
     pub db: &'a dyn DefDatabase,
     hygiene: Hygiene,
@@ -592,13 +590,10 @@ impl ExprCollector<'_> {
         let res = match res {
             Ok(res) => res,
             Err(UnresolvedMacro { path }) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall(
-                    UnresolvedMacroCall {
-                        file: outer_file,
-                        node: syntax_ptr.cast().unwrap(),
-                        path,
-                    },
-                ));
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall {
+                    node: InFile::new(outer_file, syntax_ptr),
+                    path,
+                });
                 collector(self, None);
                 return;
             }
@@ -606,21 +601,15 @@ impl ExprCollector<'_> {
 
         match &res.err {
             Some(ExpandError::UnresolvedProcMacro) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro(
-                    UnresolvedProcMacro {
-                        file: outer_file,
-                        node: syntax_ptr.into(),
-                        precise_location: None,
-                        macro_name: None,
-                    },
-                ));
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
+                    node: InFile::new(outer_file, syntax_ptr),
+                });
             }
             Some(err) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::MacroError(MacroError {
-                    file: outer_file,
-                    node: syntax_ptr.into(),
+                self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
+                    node: InFile::new(outer_file, syntax_ptr),
                     message: err.to_string(),
-                }));
+                });
             }
             None => {}
         }
@@ -945,12 +934,14 @@ impl ExprCollector<'_> {
                     return Some(());
                 }
 
-                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
-                    file: self.expander.current_file_id,
-                    node: SyntaxNodePtr::new(owner.syntax()),
+                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode {
+                    node: InFile::new(
+                        self.expander.current_file_id,
+                        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
deleted file mode 100644
index a71ae26684..0000000000
--- a/crates/hir_def/src/diagnostics.rs
+++ /dev/null
@@ -1,227 +0,0 @@
-//! 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 70001cac86..9aa95720af 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -19,7 +19,6 @@ 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;
 
@@ -56,7 +55,6 @@ use std::{
     sync::Arc,
 };
 
-use adt::VariantData;
 use base_db::{impl_intern_key, salsa, CrateId};
 use hir_expand::{
     ast_id_map::FileAstId,
@@ -67,15 +65,18 @@ use hir_expand::{
 use la_arena::Idx;
 use nameres::DefMap;
 use path::ModPath;
+use stdx::impl_from;
 use syntax::ast;
 
-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 crate::{
+    adt::VariantData,
+    attr::AttrId,
+    builtin_type::BuiltinType,
+    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 249af6fc80..ebfcc26c41 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -47,18 +47,19 @@
 //! 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::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId};
+use hir_expand::{name::Name, InFile, MacroDefId};
 use la_arena::Arena;
 use profile::Count;
 use rustc_hash::FxHashMap;
@@ -254,15 +255,6 @@ 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()
@@ -448,6 +440,11 @@ 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 {
@@ -471,236 +468,3 @@ 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 2ae740d0e6..3ea4729080 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -33,7 +33,10 @@ use crate::{
     },
     macro_call_as_call_id,
     nameres::{
-        diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint,
+        diagnostics::DefDiagnostic,
+        mod_resolution::ModDir,
+        path_resolution::ReachedFixedPoint,
+        proc_macro::{ProcMacroDef, ProcMacroKind},
         BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
     },
     path::{ImportAlias, ModPath, PathKind},
@@ -44,8 +47,6 @@ 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
new file mode 100644
index 0000000000..8f2f0ff9f5
--- /dev/null
+++ b/crates/hir_def/src/nameres/diagnostics.rs
@@ -0,0 +1,90 @@
+//! 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 45ab9d0ffb..d9ec03d2da 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(crate) fn expand_use_item(
+    pub 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_expand/src/db.rs b/crates/hir_expand/src/db.rs
index 625c26f0a0..e8f4af3095 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.node(db);
+        let node = loc.kind.to_node(db);
 
         // collect parent information for warning log
         let parents =
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 6be4516a32..10d37234e7 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -8,7 +8,6 @@ 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;
@@ -108,7 +107,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.node(db))
+                Some(loc.kind.to_node(db))
             }
         }
     }
@@ -153,7 +152,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.node(db),
+                    MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
                     _ => return None,
                 };
                 Some(item.with_value(ast::Item::cast(item.value.clone())?))
@@ -269,7 +268,7 @@ impl MacroCallKind {
         }
     }
 
-    fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
+    pub fn to_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 84fc8ce14d..7598e2193a 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -8,12 +8,14 @@ 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;
+use crate::{
+    db::HirDatabase,
+    diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
+};
 
 pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
 
@@ -446,15 +448,13 @@ 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,
-        diagnostics::{Diagnostic, DiagnosticSinkBuilder},
-    };
+    use hir_expand::db::AstDatabase;
     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 075dc4131f..ef982cbcd4 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -19,10 +19,7 @@ use hir_def::{
     src::HasSource,
     AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
 };
-use hir_expand::{
-    diagnostics::DiagnosticSink,
-    name::{AsName, Name},
-};
+use hir_expand::name::{AsName, Name};
 use stdx::{always, never};
 use syntax::{
     ast::{self, NameOwner},
@@ -32,6 +29,7 @@ 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 d1f113e7ff..86f82e3fa4 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::{diagnostics::DiagnosticSink, name};
+use hir_expand::name;
 use rustc_hash::FxHashSet;
 use syntax::{ast, AstPtr};
 
@@ -16,6 +16,7 @@ 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 5d13bddea3..c3c483425e 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, InferenceResult, Interner, TyExt, TyKind,
+    db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
+    Interner, TyExt, TyKind,
 };
 
 pub(super) struct UnsafeValidator<'a, 'b: 'a> {
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_ty/src/diagnostics_sink.rs
similarity index 99%
rename from crates/hir_expand/src/diagnostics.rs
rename to crates/hir_ty/src/diagnostics_sink.rs
index bf0b85ce9d..084fa8b06a 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics_sink.rs
@@ -16,10 +16,9 @@
 
 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_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 edb65622f1..174b7471e0 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -28,13 +28,14 @@ use hir_def::{
     AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup,
     TraitId, TypeAliasId, VariantId,
 };
-use hir_expand::{diagnostics::DiagnosticSink, name::name};
+use hir_expand::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,
@@ -580,10 +581,7 @@ impl<'a> InferenceContext<'a> {
     fn resolve_ops_try_ok(&self) -> Option<TypeAliasId> {
         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
-            .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> {
@@ -796,11 +794,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/lib.rs b/crates/hir_ty/src/lib.rs
index ef021978ad..50e0d63333 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -21,6 +21,7 @@ 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 27d347dbd9..dcac7c76d2 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, Expect};
+    use expect_test::Expect;
     use ide_assists::AssistResolveStrategy;
     use stdx::trim_indent;
-    use test_utils::assert_eq_text;
+    use test_utils::{assert_eq_text, extract_annotations};
 
     use crate::{fixture, DiagnosticsConfig};
 
@@ -396,26 +396,51 @@ 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_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",
-                            ),
-                        ),
-                    },
-                ]
-            "#]],
+        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;
+}
+"#,
         );
     }
 
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/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs
index 4d88932ca5..b2317618a8 100644
--- a/crates/rust-analyzer/src/caps.rs
+++ b/crates/rust-analyzer/src/caps.rs
@@ -1,4 +1,4 @@
-//! Advertises the capabilities of the LSP Server.
+//! Advertizes the capabilities of the LSP Server.
 use std::env;
 
 use lsp_types::{
@@ -54,7 +54,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti
         code_action_provider: Some(code_action_capabilities(client_caps)),
         code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
         document_formatting_provider: Some(OneOf::Left(true)),
-        document_range_formatting_provider: Some(OneOf::Left(true)),
+        document_range_formatting_provider: None,
         document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
             first_trigger_character: "=".to_string(),
             more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]),
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7620a2fe17..7c02a507cd 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -218,10 +218,6 @@ config_data! {
         /// Advanced option, fully override the command rust-analyzer uses for
         /// formatting.
         rustfmt_overrideCommand: Option<Vec<String>> = "null",
-        /// Enables the use of rustfmt's unstable range formatting command for the
-        /// `textDocument/rangeFormatting` request. The rustfmt option is unstable and only
-        /// available on a nightly build.
-        rustfmt_enableRangeFormatting: bool = "false",
 
         /// Workspace symbol search scope.
         workspace_symbol_search_scope: WorskpaceSymbolSearchScopeDef = "\"workspace\"",
@@ -309,7 +305,7 @@ pub struct NotificationsConfig {
 
 #[derive(Debug, Clone)]
 pub enum RustfmtConfig {
-    Rustfmt { extra_args: Vec<String>, enable_range_formatting: bool },
+    Rustfmt { extra_args: Vec<String> },
     CustomCommand { command: String, args: Vec<String> },
 }
 
@@ -588,10 +584,9 @@ impl Config {
                 let command = args.remove(0);
                 RustfmtConfig::CustomCommand { command, args }
             }
-            Some(_) | None => RustfmtConfig::Rustfmt {
-                extra_args: self.data.rustfmt_extraArgs.clone(),
-                enable_range_formatting: self.data.rustfmt_enableRangeFormatting,
-            },
+            Some(_) | None => {
+                RustfmtConfig::Rustfmt { extra_args: self.data.rustfmt_extraArgs.clone() }
+            }
         }
     }
     pub fn flycheck(&self) -> Option<FlycheckConfig> {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 4567446036..f482104248 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -27,7 +27,7 @@ use lsp_types::{
 };
 use project_model::TargetKind;
 use serde::{Deserialize, Serialize};
-use serde_json::{json, to_value};
+use serde_json::to_value;
 use stdx::format_to;
 use syntax::{algo, ast, AstNode, TextRange, TextSize};
 
@@ -955,17 +955,104 @@ pub(crate) fn handle_formatting(
     params: DocumentFormattingParams,
 ) -> Result<Option<Vec<lsp_types::TextEdit>>> {
     let _p = profile::span("handle_formatting");
+    let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
+    let file = snap.analysis.file_text(file_id)?;
+    let crate_ids = snap.analysis.crate_for(file_id)?;
 
-    run_rustfmt(&snap, params.text_document, None)
-}
+    let line_index = snap.file_line_index(file_id)?;
 
-pub(crate) fn handle_range_formatting(
-    snap: GlobalStateSnapshot,
-    params: lsp_types::DocumentRangeFormattingParams,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
-    let _p = profile::span("handle_range_formatting");
+    let mut rustfmt = match snap.config.rustfmt() {
+        RustfmtConfig::Rustfmt { extra_args } => {
+            let mut cmd = process::Command::new(toolchain::rustfmt());
+            cmd.args(extra_args);
+            // try to chdir to the file so we can respect `rustfmt.toml`
+            // FIXME: use `rustfmt --config-path` once
+            // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
+            match params.text_document.uri.to_file_path() {
+                Ok(mut path) => {
+                    // pop off file name
+                    if path.pop() && path.is_dir() {
+                        cmd.current_dir(path);
+                    }
+                }
+                Err(_) => {
+                    log::error!(
+                        "Unable to get file path for {}, rustfmt.toml might be ignored",
+                        params.text_document.uri
+                    );
+                }
+            }
+            if let Some(&crate_id) = crate_ids.first() {
+                // Assume all crates are in the same edition
+                let edition = snap.analysis.crate_edition(crate_id)?;
+                cmd.arg("--edition");
+                cmd.arg(edition.to_string());
+            }
+            cmd
+        }
+        RustfmtConfig::CustomCommand { command, args } => {
+            let mut cmd = process::Command::new(command);
+            cmd.args(args);
+            cmd
+        }
+    };
+
+    let mut rustfmt =
+        rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
+
+    rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
 
-    run_rustfmt(&snap, params.text_document, Some(params.range))
+    let output = rustfmt.wait_with_output()?;
+    let captured_stdout = String::from_utf8(output.stdout)?;
+    let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
+
+    if !output.status.success() {
+        let rustfmt_not_installed =
+            captured_stderr.contains("not installed") || captured_stderr.contains("not available");
+
+        return match output.status.code() {
+            Some(1) if !rustfmt_not_installed => {
+                // While `rustfmt` doesn't have a specific exit code for parse errors this is the
+                // likely cause exiting with 1. Most Language Servers swallow parse errors on
+                // formatting because otherwise an error is surfaced to the user on top of the
+                // syntax error diagnostics they're already receiving. This is especially jarring
+                // if they have format on save enabled.
+                log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
+                Ok(None)
+            }
+            _ => {
+                // Something else happened - e.g. `rustfmt` is missing or caught a signal
+                Err(LspError::new(
+                    -32900,
+                    format!(
+                        r#"rustfmt exited with:
+                           Status: {}
+                           stdout: {}
+                           stderr: {}"#,
+                        output.status, captured_stdout, captured_stderr,
+                    ),
+                )
+                .into())
+            }
+        };
+    }
+
+    let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
+
+    if line_index.endings != new_line_endings {
+        // If line endings are different, send the entire file.
+        // Diffing would not work here, as the line endings might be the only
+        // difference.
+        Ok(Some(to_proto::text_edit_vec(
+            &line_index,
+            TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
+        )))
+    } else if *file == new_text {
+        // The document is already formatted correctly -- no edits needed.
+        Ok(None)
+    } else {
+        Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
+    }
 }
 
 pub(crate) fn handle_code_action(
@@ -1588,140 +1675,6 @@ fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>)
     }
 }
 
-fn run_rustfmt(
-    snap: &GlobalStateSnapshot,
-    text_document: TextDocumentIdentifier,
-    range: Option<lsp_types::Range>,
-) -> Result<Option<Vec<lsp_types::TextEdit>>> {
-    let file_id = from_proto::file_id(&snap, &text_document.uri)?;
-    let file = snap.analysis.file_text(file_id)?;
-    let crate_ids = snap.analysis.crate_for(file_id)?;
-
-    let line_index = snap.file_line_index(file_id)?;
-
-    let mut rustfmt = match snap.config.rustfmt() {
-        RustfmtConfig::Rustfmt { extra_args, enable_range_formatting } => {
-            let mut cmd = process::Command::new(toolchain::rustfmt());
-            cmd.args(extra_args);
-            // try to chdir to the file so we can respect `rustfmt.toml`
-            // FIXME: use `rustfmt --config-path` once
-            // https://github.com/rust-lang/rustfmt/issues/4660 gets fixed
-            match text_document.uri.to_file_path() {
-                Ok(mut path) => {
-                    // pop off file name
-                    if path.pop() && path.is_dir() {
-                        cmd.current_dir(path);
-                    }
-                }
-                Err(_) => {
-                    log::error!(
-                        "Unable to get file path for {}, rustfmt.toml might be ignored",
-                        text_document.uri
-                    );
-                }
-            }
-            if let Some(&crate_id) = crate_ids.first() {
-                // Assume all crates are in the same edition
-                let edition = snap.analysis.crate_edition(crate_id)?;
-                cmd.arg("--edition");
-                cmd.arg(edition.to_string());
-            }
-
-            if let Some(range) = range {
-                if !enable_range_formatting {
-                    return Err(LspError::new(
-                        ErrorCode::InvalidRequest as i32,
-                        String::from(
-                            "rustfmt range formatting is unstable. \
-                            Opt-in by using a nightly build of rustfmt and setting \
-                            `rustfmt.enableRangeFormatting` to true in your LSP configuration",
-                        ),
-                    )
-                    .into());
-                }
-
-                let frange = from_proto::file_range(&snap, text_document.clone(), range)?;
-                let start_line = line_index.index.line_col(frange.range.start()).line;
-                let end_line = line_index.index.line_col(frange.range.end()).line;
-
-                cmd.arg("--unstable-features");
-                cmd.arg("--file-lines");
-                cmd.arg(
-                    json!([{
-                        "file": "stdin",
-                        "range": [start_line, end_line]
-                    }])
-                    .to_string(),
-                );
-            }
-
-            cmd
-        }
-        RustfmtConfig::CustomCommand { command, args } => {
-            let mut cmd = process::Command::new(command);
-            cmd.args(args);
-            cmd
-        }
-    };
-
-    let mut rustfmt =
-        rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
-
-    rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?;
-
-    let output = rustfmt.wait_with_output()?;
-    let captured_stdout = String::from_utf8(output.stdout)?;
-    let captured_stderr = String::from_utf8(output.stderr).unwrap_or_default();
-
-    if !output.status.success() {
-        let rustfmt_not_installed =
-            captured_stderr.contains("not installed") || captured_stderr.contains("not available");
-
-        return match output.status.code() {
-            Some(1) if !rustfmt_not_installed => {
-                // While `rustfmt` doesn't have a specific exit code for parse errors this is the
-                // likely cause exiting with 1. Most Language Servers swallow parse errors on
-                // formatting because otherwise an error is surfaced to the user on top of the
-                // syntax error diagnostics they're already receiving. This is especially jarring
-                // if they have format on save enabled.
-                log::info!("rustfmt exited with status 1, assuming parse error and ignoring");
-                Ok(None)
-            }
-            _ => {
-                // Something else happened - e.g. `rustfmt` is missing or caught a signal
-                Err(LspError::new(
-                    -32900,
-                    format!(
-                        r#"rustfmt exited with:
-                           Status: {}
-                           stdout: {}
-                           stderr: {}"#,
-                        output.status, captured_stdout, captured_stderr,
-                    ),
-                )
-                .into())
-            }
-        };
-    }
-
-    let (new_text, new_line_endings) = LineEndings::normalize(captured_stdout);
-
-    if line_index.endings != new_line_endings {
-        // If line endings are different, send the entire file.
-        // Diffing would not work here, as the line endings might be the only
-        // difference.
-        Ok(Some(to_proto::text_edit_vec(
-            &line_index,
-            TextEdit::replace(TextRange::up_to(TextSize::of(&*file)), new_text),
-        )))
-    } else if *file == new_text {
-        // The document is already formatted correctly -- no edits needed.
-        Ok(None)
-    } else {
-        Ok(Some(to_proto::text_edit_vec(&line_index, diff(&file, &new_text))))
-    }
-}
-
 #[derive(Debug, Serialize, Deserialize)]
 struct CompletionResolveData {
     position: lsp_types::TextDocumentPositionParams,
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 008758ea0d..cb002f7009 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -543,7 +543,6 @@ impl GlobalState {
             .on::<lsp_types::request::Rename>(handlers::handle_rename)
             .on::<lsp_types::request::References>(handlers::handle_references)
             .on::<lsp_types::request::Formatting>(handlers::handle_formatting)
-            .on::<lsp_types::request::RangeFormatting>(handlers::handle_range_formatting)
             .on::<lsp_types::request::DocumentHighlightRequest>(handlers::handle_document_highlight)
             .on::<lsp_types::request::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)
             .on::<lsp_types::request::CallHierarchyIncomingCalls>(
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index f3da82febb..c02bab7cc3 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -346,13 +346,6 @@ Additional arguments to `rustfmt`.
 Advanced option, fully override the command rust-analyzer uses for
 formatting.
 --
-[[rust-analyzer.rustfmt.enableRangeFormatting]]rust-analyzer.rustfmt.enableRangeFormatting (default: `false`)::
-+
---
-Enables the use of rustfmt's unstable range formatting command for the
-`textDocument/rangeFormatting` request. The rustfmt option is unstable and only
-available on a nightly build.
---
 [[rust-analyzer.workspace.symbol.search.scope]]rust-analyzer.workspace.symbol.search.scope (default: `"workspace"`)::
 +
 --
diff --git a/editors/code/package.json b/editors/code/package.json
index 05cbccf946..17d9281ff7 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -795,11 +795,6 @@
                         "type": "string"
                     }
                 },
-                "rust-analyzer.rustfmt.enableRangeFormatting": {
-                    "markdownDescription": "Enables the use of rustfmt's unstable range formatting command for the\n`textDocument/rangeFormatting` request. The rustfmt option is unstable and only\navailable on a nightly build.",
-                    "default": false,
-                    "type": "boolean"
-                },
                 "rust-analyzer.workspace.symbol.search.scope": {
                     "markdownDescription": "Workspace symbol search scope.",
                     "default": "workspace",


Test patch:
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index 3e8f16306b..d4fae05a69 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
-  //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+  //^^^^^^^^^^^^^^^^^^^ InactiveCode
     #[cfg(a)] {}         // Expression statement
-  //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+  //^^^^^^^^^^^^ InactiveCode
     #[cfg(a)] let x = 0; // let statement
-  //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+  //^^^^^^^^^^^^^^^^^^^^ InactiveCode
 
     abc(#[cfg(a)] 0);
-      //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+      //^^^^^^^^^^^ InactiveCode
     let x = Struct {
         #[cfg(a)] f: 0,
-      //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+      //^^^^^^^^^^^^^^ InactiveCode
     };
     match () {
         () => (),
         #[cfg(a)] () => (),
-      //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+      //^^^^^^^^^^^^^^^^^^ InactiveCode
     }
 
     #[cfg(a)] 0          // Trailing expression of block
-  //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
+  //^^^^^^^^^^^ InactiveCode
 }
     ",
     );
@@ -188,7 +188,7 @@ fn unresolved_macro_diag() {
         r#"
 fn f() {
     m!();
-  //^^^^ unresolved macro `m!`
+  //^^^^ UnresolvedMacroCall
 }
       "#,
     );
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 75147d973a..ec66709528 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -18,40 +18,13 @@ fn unresolved_import() {
         r"
         use does_exist;
         use does_not_exist;
-          //^^^^^^^^^^^^^^ unresolved import
+      //^^^^^^^^^^^^^^^^^^^ UnresolvedImport
 
         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(
@@ -59,7 +32,7 @@ fn unresolved_extern_crate() {
         //- /main.rs crate:main deps:core
         extern crate core;
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
         //- /lib.rs crate:core
         ",
     );
@@ -72,7 +45,7 @@ fn extern_crate_self_as() {
         r"
         //- /lib.rs
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
         // Should not error.
         extern crate self as foo;
         struct Foo;
@@ -88,18 +61,18 @@ fn dedup_unresolved_import_from_unresolved_crate() {
         //- /main.rs crate:main
         mod a {
             extern crate doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
+          //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
 
             // Should not error, since we already errored for the missing crate.
             use doesnotexist::{self, bla, *};
 
             use crate::doesnotexist;
-              //^^^^^^^^^^^^^^^^^^^ unresolved import
+          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
         }
 
         mod m {
             use super::doesnotexist;
-              //^^^^^^^^^^^^^^^^^^^ unresolved import
+          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
         }
         ",
     );
@@ -112,7 +85,7 @@ fn unresolved_module() {
         //- /lib.rs
         mod foo;
           mod bar;
-        //^^^^^^^^ unresolved module
+        //^^^^^^^^ UnresolvedModule
         mod baz {}
         //- /foo.rs
         ",
@@ -127,16 +100,16 @@ fn inactive_item() {
         r#"
         //- /lib.rs
           #[cfg(no)] pub fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
 
           #[cfg(no)] #[cfg(no2)] mod m;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
 
           #[cfg(all(not(a), b))] enum E {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
 
           #[cfg(feature = "std")] use std;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
         "#,
     );
 }
@@ -149,14 +122,14 @@ fn inactive_via_cfg_attr() {
         r#"
         //- /lib.rs
           #[cfg_attr(not(never), cfg(no))] fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
 
           #[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() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
         "#,
     );
 }
@@ -170,7 +143,7 @@ fn unresolved_legacy_scope_macro() {
 
           m!();
           m2!();
-        //^^^^^^ unresolved macro `self::m2!`
+        //^^^^^^ UnresolvedMacroCall
         "#,
     );
 }
@@ -187,7 +160,7 @@ fn unresolved_module_scope_macro() {
 
           self::m!();
           self::m2!();
-        //^^^^^^^^^^^^ unresolved macro `self::m2!`
+        //^^^^^^^^^^^^ UnresolvedMacroCall
         "#,
     );
 }
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index 8fa703a574..6c357c915b 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -5,19 +5,20 @@ use std::{
     sync::{Arc, Mutex},
 };
 
-use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast};
+use base_db::{
+    salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, 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, TextRange, TextSize};
+use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
 use test_utils::extract_annotations;
 
 use crate::{
+    body::BodyDiagnostic,
     db::DefDatabase,
-    nameres::{DefMap, ModuleSource},
+    nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
     src::HasSource,
     LocalModuleId, Lookup, ModuleDefId, ModuleId,
 };
@@ -262,19 +263,70 @@ impl TestDB {
             .collect()
     }
 
-    pub(crate) fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
+    pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
         let crate_graph = self.crate_graph();
         for krate in crate_graph.iter() {
             let crate_def_map = self.crate_def_map(krate);
 
-            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 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())
+            }
 
+            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;
-                        source_map.add_diagnostics(self, &mut sink);
+                        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())
+                        }
                     }
                 }
             }
@@ -287,14 +339,7 @@ impl TestDB {
         assert!(!annotations.is_empty());
 
         let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
-        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();
+        db.diagnostics(&mut |frange, message| {
             actual.entry(frange.file_id).or_default().push((frange.range, message));
         });
 
@@ -319,7 +364,7 @@ impl TestDB {
         assert!(annotations.is_empty());
 
         let mut has_diagnostics = false;
-        db.diagnostics(|_| {
+        db.diagnostics(&mut |_, _| {
             has_diagnostics = true;
         });
 
diff --git a/crates/hir_ty/src/tests/traits.rs b/crates/hir_ty/src/tests/traits.rs
index 6cd8786eae..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,25 +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()': ()
-        "#]],
-    );
-}