Repository: rust-analyzer/rust-analyzer
Pull request #9002 (2021-05-26 10:26:05)

minor: Move annotations below item attributes
This moves annotations/code lenses below attributes in items, bringing them inline with functions where this is already the case. This is done by changing the annotations covering range to just the name node's range which is also more inline with what the lsp expects which is that the range should ideally only cover a single line. 

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/9000
bors r+


Patch:
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 22ec7c6aca..414c3f35ee 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -3,227 +3,13 @@
 //!
 //! This probably isn't the best way to do this -- ideally, diagnistics should
 //! be expressed in terms of hir types themselves.
-use std::any::Any;
-
-use cfg::{CfgExpr, CfgOptions, DnfExpr};
-use hir_def::path::ModPath;
-use hir_expand::{HirFileId, InFile};
-use stdx::format_to;
-use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
-
-pub use hir_ty::{
-    diagnostics::{
-        IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
-        MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
-        ReplaceFilterMapNextWithFindMap,
-    },
-    diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
+pub use hir_def::diagnostics::{
+    InactiveCode, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
+};
+pub use hir_expand::diagnostics::{
+    Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
+};
+pub use hir_ty::diagnostics::{
+    IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
+    NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
 };
-
-// Diagnostic: unresolved-module
-//
-// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
-#[derive(Debug)]
-pub struct UnresolvedModule {
-    pub file: HirFileId,
-    pub decl: AstPtr<ast::Module>,
-    pub candidate: String,
-}
-
-impl Diagnostic for UnresolvedModule {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-module")
-    }
-    fn message(&self) -> String {
-        "unresolved module".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.decl.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: unresolved-extern-crate
-//
-// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
-#[derive(Debug)]
-pub struct UnresolvedExternCrate {
-    pub file: HirFileId,
-    pub item: AstPtr<ast::ExternCrate>,
-}
-
-impl Diagnostic for UnresolvedExternCrate {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-extern-crate")
-    }
-    fn message(&self) -> String {
-        "unresolved extern crate".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.item.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-#[derive(Debug)]
-pub struct UnresolvedImport {
-    pub file: HirFileId,
-    pub node: AstPtr<ast::UseTree>,
-}
-
-impl Diagnostic for UnresolvedImport {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-import")
-    }
-    fn message(&self) -> String {
-        "unresolved import".to_string()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        // This currently results in false positives in the following cases:
-        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
-        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
-        // - proc macros and/or proc macro generated code
-        true
-    }
-}
-
-// Diagnostic: unresolved-macro-call
-//
-// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
-// macro in a macro invocation.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct UnresolvedMacroCall {
-    pub file: HirFileId,
-    pub node: AstPtr<ast::MacroCall>,
-    pub path: ModPath,
-}
-
-impl Diagnostic for UnresolvedMacroCall {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-macro-call")
-    }
-    fn message(&self) -> String {
-        format!("unresolved macro `{}!`", self.path)
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone().into())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        true
-    }
-}
-
-// Diagnostic: inactive-code
-//
-// This diagnostic is shown for code with inactive `#[cfg]` attributes.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct InactiveCode {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    pub cfg: CfgExpr,
-    pub opts: CfgOptions,
-}
-
-impl Diagnostic for InactiveCode {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("inactive-code")
-    }
-    fn message(&self) -> String {
-        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
-        let mut buf = "code is inactive due to #[cfg] directives".to_string();
-
-        if let Some(inactive) = inactive {
-            format_to!(buf, ": {}", inactive);
-        }
-
-        buf
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: unresolved-proc-macro
-//
-// This diagnostic is shown when a procedural macro can not be found. This usually means that
-// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
-// but can also indicate project setup problems.
-//
-// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
-// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
-// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct UnresolvedProcMacro {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
-    /// to use instead.
-    pub precise_location: Option<TextRange>,
-    pub macro_name: Option<String>,
-}
-
-impl Diagnostic for UnresolvedProcMacro {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("unresolved-proc-macro")
-    }
-
-    fn message(&self) -> String {
-        match &self.macro_name {
-            Some(name) => format!("proc macro `{}` not expanded", name),
-            None => "proc macro not expanded".to_string(),
-        }
-    }
-
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-}
-
-// Diagnostic: macro-error
-//
-// This diagnostic is shown for macro expansion errors.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct MacroError {
-    pub file: HirFileId,
-    pub node: SyntaxNodePtr,
-    pub message: String,
-}
-
-impl Diagnostic for MacroError {
-    fn code(&self) -> DiagnosticCode {
-        DiagnosticCode("macro-error")
-    }
-    fn message(&self) -> String {
-        self.message.clone()
-    }
-    fn display_source(&self) -> InFile<SyntaxNodePtr> {
-        InFile::new(self.file, self.node.clone())
-    }
-    fn as_any(&self) -> &(dyn Any + Send + 'static) {
-        self
-    }
-    fn is_experimental(&self) -> bool {
-        // Newly added and not very well-tested, might contain false positives.
-        true
-    }
-}
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 1ecd2391b0..cdf65a0445 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -35,18 +35,12 @@ use std::{iter, sync::Arc};
 
 use arrayvec::ArrayVec;
 use base_db::{CrateDisplayName, CrateId, Edition, FileId};
-use diagnostics::{
-    InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
-    UnresolvedModule, UnresolvedProcMacro,
-};
 use either::Either;
 use hir_def::{
     adt::{ReprKind, VariantData},
-    body::BodyDiagnostic,
     expr::{BindingAnnotation, LabelId, Pat, PatId},
     item_tree::ItemTreeNode,
     lang_item::LangItemTarget,
-    nameres,
     per_ns::PerNs,
     resolver::{HasResolver, Resolver},
     src::HasSource as _,
@@ -56,12 +50,11 @@ use hir_def::{
     LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
     TypeParamId, UnionId,
 };
-use hir_expand::{name::name, MacroCallKind, MacroDefKind};
+use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
 use hir_ty::{
     autoderef,
     consteval::ConstExt,
     could_unify,
-    diagnostics_sink::DiagnosticSink,
     method_resolution::{self, def_crates, TyFingerprint},
     primitive::UintTy,
     subst_prefix,
@@ -72,12 +65,11 @@ use hir_ty::{
     WhereClause,
 };
 use itertools::Itertools;
-use nameres::diagnostics::DefDiagnosticKind;
 use rustc_hash::FxHashSet;
 use stdx::{format_to, impl_from};
 use syntax::{
     ast::{self, AttrsOwner, NameOwner},
-    AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr,
+    AstNode, SmolStr,
 };
 use tt::{Ident, Leaf, Literal, TokenTree};
 
@@ -450,137 +442,7 @@ impl Module {
             format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
         });
         let def_map = self.id.def_map(db.upcast());
-        for diag in def_map.diagnostics() {
-            if diag.in_module != self.id.local_id {
-                // FIXME: This is accidentally quadratic.
-                continue;
-            }
-            match &diag.kind {
-                DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => {
-                    let decl = declaration.to_node(db.upcast());
-                    sink.push(UnresolvedModule {
-                        file: declaration.file_id,
-                        decl: AstPtr::new(&decl),
-                        candidate: candidate.clone(),
-                    })
-                }
-                DefDiagnosticKind::UnresolvedExternCrate { ast } => {
-                    let item = ast.to_node(db.upcast());
-                    sink.push(UnresolvedExternCrate {
-                        file: ast.file_id,
-                        item: AstPtr::new(&item),
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedImport { ast, index } => {
-                    let use_item = ast.to_node(db.upcast());
-                    let hygiene = Hygiene::new(db.upcast(), ast.file_id);
-                    let mut cur = 0;
-                    let mut tree = None;
-                    ModPath::expand_use_item(
-                        db.upcast(),
-                        InFile::new(ast.file_id, use_item),
-                        &hygiene,
-                        |_mod_path, use_tree, _is_glob, _alias| {
-                            if cur == *index {
-                                tree = Some(use_tree.clone());
-                            }
-
-                            cur += 1;
-                        },
-                    );
-
-                    if let Some(tree) = tree {
-                        sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
-                    }
-                }
-
-                DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
-                    let item = ast.to_node(db.upcast());
-                    sink.push(InactiveCode {
-                        file: ast.file_id,
-                        node: AstPtr::new(&item).into(),
-                        cfg: cfg.clone(),
-                        opts: opts.clone(),
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedProcMacro { ast } => {
-                    let mut precise_location = None;
-                    let (file, ast, name) = match ast {
-                        MacroCallKind::FnLike { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
-                        }
-                        MacroCallKind::Derive { ast_id, derive_name, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-
-                            // Compute the precise location of the macro name's token in the derive
-                            // list.
-                            // FIXME: This does not handle paths to the macro, but neither does the
-                            // rest of r-a.
-                            let derive_attrs =
-                                node.attrs().filter_map(|attr| match attr.as_simple_call() {
-                                    Some((name, args)) if name == "derive" => Some(args),
-                                    _ => None,
-                                });
-                            'outer: for attr in derive_attrs {
-                                let tokens =
-                                    attr.syntax().children_with_tokens().filter_map(|elem| {
-                                        match elem {
-                                            syntax::NodeOrToken::Node(_) => None,
-                                            syntax::NodeOrToken::Token(tok) => Some(tok),
-                                        }
-                                    });
-                                for token in tokens {
-                                    if token.kind() == SyntaxKind::IDENT
-                                        && token.text() == derive_name.as_str()
-                                    {
-                                        precise_location = Some(token.text_range());
-                                        break 'outer;
-                                    }
-                                }
-                            }
-
-                            (
-                                ast_id.file_id,
-                                SyntaxNodePtr::from(AstPtr::new(&node)),
-                                Some(derive_name.clone()),
-                            )
-                        }
-                    };
-                    sink.push(UnresolvedProcMacro {
-                        file,
-                        node: ast,
-                        precise_location,
-                        macro_name: name,
-                    });
-                }
-
-                DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
-                    let node = ast.to_node(db.upcast());
-                    sink.push(UnresolvedMacroCall {
-                        file: ast.file_id,
-                        node: AstPtr::new(&node),
-                        path: path.clone(),
-                    });
-                }
-
-                DefDiagnosticKind::MacroError { ast, message } => {
-                    let (file, ast) = match ast {
-                        MacroCallKind::FnLike { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
-                        }
-                        MacroCallKind::Derive { ast_id, .. } => {
-                            let node = ast_id.to_node(db.upcast());
-                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
-                        }
-                    };
-                    sink.push(MacroError { file, node: ast, message: message.clone() });
-                }
-            }
-        }
+        def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
         for decl in self.declarations(db) {
             match decl {
                 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
@@ -1003,37 +865,7 @@ impl Function {
 
     pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
         let krate = self.module(db).id.krate();
-
-        let source_map = db.body_with_source_map(self.id.into()).1;
-        for diag in source_map.diagnostics() {
-            match diag {
-                BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode {
-                    file: node.file_id,
-                    node: node.value.clone(),
-                    cfg: cfg.clone(),
-                    opts: opts.clone(),
-                }),
-                BodyDiagnostic::MacroError { node, message } => sink.push(MacroError {
-                    file: node.file_id,
-                    node: node.value.clone().into(),
-                    message: message.to_string(),
-                }),
-                BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro {
-                    file: node.file_id,
-                    node: node.value.clone().into(),
-                    precise_location: None,
-                    macro_name: None,
-                }),
-                BodyDiagnostic::UnresolvedMacroCall { node, path } => {
-                    sink.push(UnresolvedMacroCall {
-                        file: node.file_id,
-                        node: node.value.clone(),
-                        path: path.clone(),
-                    })
-                }
-            }
-        }
-
+        hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
         hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
         hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
     }
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index c521879c87..98b485b602 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,6 +1,7 @@
 //! Defines `Body`: a lowered representation of bodies of functions, statics and
 //! consts.
 mod lower;
+mod diagnostics;
 #[cfg(test)]
 mod tests;
 pub mod scope;
@@ -8,16 +9,17 @@ pub mod scope;
 use std::{mem, ops::Index, sync::Arc};
 
 use base_db::CrateId;
-use cfg::{CfgExpr, CfgOptions};
+use cfg::CfgOptions;
 use drop_bomb::DropBomb;
 use either::Either;
 use hir_expand::{
-    ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
+    ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult,
+    HirFileId, InFile, MacroDefId,
 };
 use la_arena::{Arena, ArenaMap};
 use profile::Count;
 use rustc_hash::FxHashMap;
-use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
+use syntax::{ast, AstNode, AstPtr};
 
 use crate::{
     attr::{Attrs, RawAttrs},
@@ -271,20 +273,12 @@ pub struct BodySourceMap {
 
     /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
     /// the source map (since they're just as volatile).
-    diagnostics: Vec<BodyDiagnostic>,
+    diagnostics: Vec<diagnostics::BodyDiagnostic>,
 }
 
 #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
 pub struct SyntheticSyntax;
 
-#[derive(Debug, Eq, PartialEq)]
-pub enum BodyDiagnostic {
-    InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
-    MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
-    UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
-    UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
-}
-
 impl Body {
     pub(crate) fn body_with_source_map_query(
         db: &dyn DefDatabase,
@@ -422,8 +416,9 @@ impl BodySourceMap {
         self.field_map.get(&src).cloned()
     }
 
-    /// Get a reference to the body source map's diagnostics.
-    pub fn diagnostics(&self) -> &[BodyDiagnostic] {
-        &self.diagnostics
+    pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
+        for diag in &self.diagnostics {
+            diag.add_to(sink);
+        }
     }
 }
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
new file mode 100644
index 0000000000..f6992c9a8a
--- /dev/null
+++ b/crates/hir_def/src/body/diagnostics.rs
@@ -0,0 +1,32 @@
+//! Diagnostics emitted during body lowering.
+
+use hir_expand::diagnostics::DiagnosticSink;
+
+use crate::diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro};
+
+#[derive(Debug, Eq, PartialEq)]
+pub(crate) enum BodyDiagnostic {
+    InactiveCode(InactiveCode),
+    MacroError(MacroError),
+    UnresolvedProcMacro(UnresolvedProcMacro),
+    UnresolvedMacroCall(UnresolvedMacroCall),
+}
+
+impl BodyDiagnostic {
+    pub(crate) fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
+        match self {
+            BodyDiagnostic::InactiveCode(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::MacroError(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::UnresolvedProcMacro(diag) => {
+                sink.push(diag.clone());
+            }
+            BodyDiagnostic::UnresolvedMacroCall(diag) => {
+                sink.push(diag.clone());
+            }
+        }
+    }
+}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index da1fdac33a..2a7e0205f5 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -8,7 +8,7 @@ use hir_expand::{
     ast_id_map::{AstIdMap, FileAstId},
     hygiene::Hygiene,
     name::{name, AsName, Name},
-    ExpandError, HirFileId, InFile,
+    ExpandError, HirFileId,
 };
 use la_arena::Arena;
 use profile::Count;
@@ -23,9 +23,9 @@ use syntax::{
 use crate::{
     adt::StructKind,
     body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax},
-    body::{BodyDiagnostic, ExprSource, PatSource},
     builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
     db::DefDatabase,
+    diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro},
     expr::{
         dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Label,
         LabelId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField,
@@ -38,6 +38,8 @@ use crate::{
     AdtId, BlockLoc, ModuleDefId, UnresolvedMacro,
 };
 
+use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
+
 pub struct LowerCtx<'a> {
     pub db: &'a dyn DefDatabase,
     hygiene: Hygiene,
@@ -590,10 +592,13 @@ impl ExprCollector<'_> {
         let res = match res {
             Ok(res) => res,
             Err(UnresolvedMacro { path }) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall {
-                    node: InFile::new(outer_file, syntax_ptr),
-                    path,
-                });
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall(
+                    UnresolvedMacroCall {
+                        file: outer_file,
+                        node: syntax_ptr.cast().unwrap(),
+                        path,
+                    },
+                ));
                 collector(self, None);
                 return;
             }
@@ -601,15 +606,21 @@ impl ExprCollector<'_> {
 
         match &res.err {
             Some(ExpandError::UnresolvedProcMacro) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro {
-                    node: InFile::new(outer_file, syntax_ptr),
-                });
+                self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro(
+                    UnresolvedProcMacro {
+                        file: outer_file,
+                        node: syntax_ptr.into(),
+                        precise_location: None,
+                        macro_name: None,
+                    },
+                ));
             }
             Some(err) => {
-                self.source_map.diagnostics.push(BodyDiagnostic::MacroError {
-                    node: InFile::new(outer_file, syntax_ptr),
+                self.source_map.diagnostics.push(BodyDiagnostic::MacroError(MacroError {
+                    file: outer_file,
+                    node: syntax_ptr.into(),
                     message: err.to_string(),
-                });
+                }));
             }
             None => {}
         }
@@ -934,14 +945,12 @@ impl ExprCollector<'_> {
                     return Some(());
                 }
 
-                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode {
-                    node: InFile::new(
-                        self.expander.current_file_id,
-                        SyntaxNodePtr::new(owner.syntax()),
-                    ),
+                self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
+                    file: self.expander.current_file_id,
+                    node: SyntaxNodePtr::new(owner.syntax()),
                     cfg,
                     opts: self.expander.cfg_options().clone(),
-                });
+                }));
 
                 None
             }
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
new file mode 100644
index 0000000000..a71ae26684
--- /dev/null
+++ b/crates/hir_def/src/diagnostics.rs
@@ -0,0 +1,227 @@
+//! Diagnostics produced by `hir_def`.
+
+use std::any::Any;
+use stdx::format_to;
+
+use cfg::{CfgExpr, CfgOptions, DnfExpr};
+use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
+use hir_expand::{HirFileId, InFile};
+use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
+
+use crate::{db::DefDatabase, path::ModPath, DefWithBodyId};
+
+pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
+    let source_map = db.body_with_source_map(owner).1;
+    source_map.add_diagnostics(db, sink);
+}
+
+// Diagnostic: unresolved-module
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
+#[derive(Debug)]
+pub struct UnresolvedModule {
+    pub file: HirFileId,
+    pub decl: AstPtr<ast::Module>,
+    pub candidate: String,
+}
+
+impl Diagnostic for UnresolvedModule {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-module")
+    }
+    fn message(&self) -> String {
+        "unresolved module".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.decl.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-extern-crate
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
+#[derive(Debug)]
+pub struct UnresolvedExternCrate {
+    pub file: HirFileId,
+    pub item: AstPtr<ast::ExternCrate>,
+}
+
+impl Diagnostic for UnresolvedExternCrate {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-extern-crate")
+    }
+    fn message(&self) -> String {
+        "unresolved extern crate".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.item.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-import
+//
+// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
+#[derive(Debug)]
+pub struct UnresolvedImport {
+    pub file: HirFileId,
+    pub node: AstPtr<ast::UseTree>,
+}
+
+impl Diagnostic for UnresolvedImport {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-import")
+    }
+    fn message(&self) -> String {
+        "unresolved import".to_string()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        // This currently results in false positives in the following cases:
+        // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
+        // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
+        // - proc macros and/or proc macro generated code
+        true
+    }
+}
+
+// Diagnostic: unresolved-macro-call
+//
+// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
+// macro in a macro invocation.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedMacroCall {
+    pub file: HirFileId,
+    pub node: AstPtr<ast::MacroCall>,
+    pub path: ModPath,
+}
+
+impl Diagnostic for UnresolvedMacroCall {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-macro-call")
+    }
+    fn message(&self) -> String {
+        format!("unresolved macro `{}!`", self.path)
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone().into())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        true
+    }
+}
+
+// Diagnostic: inactive-code
+//
+// This diagnostic is shown for code with inactive `#[cfg]` attributes.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct InactiveCode {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    pub cfg: CfgExpr,
+    pub opts: CfgOptions,
+}
+
+impl Diagnostic for InactiveCode {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("inactive-code")
+    }
+    fn message(&self) -> String {
+        let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
+        let mut buf = "code is inactive due to #[cfg] directives".to_string();
+
+        if let Some(inactive) = inactive {
+            format_to!(buf, ": {}", inactive);
+        }
+
+        buf
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: unresolved-proc-macro
+//
+// This diagnostic is shown when a procedural macro can not be found. This usually means that
+// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
+// but can also indicate project setup problems.
+//
+// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
+// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
+// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnresolvedProcMacro {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
+    /// to use instead.
+    pub precise_location: Option<TextRange>,
+    pub macro_name: Option<String>,
+}
+
+impl Diagnostic for UnresolvedProcMacro {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("unresolved-proc-macro")
+    }
+
+    fn message(&self) -> String {
+        match &self.macro_name {
+            Some(name) => format!("proc macro `{}` not expanded", name),
+            None => "proc macro not expanded".to_string(),
+        }
+    }
+
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
+// Diagnostic: macro-error
+//
+// This diagnostic is shown for macro expansion errors.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct MacroError {
+    pub file: HirFileId,
+    pub node: SyntaxNodePtr,
+    pub message: String,
+}
+
+impl Diagnostic for MacroError {
+    fn code(&self) -> DiagnosticCode {
+        DiagnosticCode("macro-error")
+    }
+    fn message(&self) -> String {
+        self.message.clone()
+    }
+    fn display_source(&self) -> InFile<SyntaxNodePtr> {
+        InFile::new(self.file, self.node.clone())
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+    fn is_experimental(&self) -> bool {
+        // Newly added and not very well-tested, might contain false positives.
+        true
+    }
+}
diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs
index 9aa95720af..70001cac86 100644
--- a/crates/hir_def/src/lib.rs
+++ b/crates/hir_def/src/lib.rs
@@ -19,6 +19,7 @@ pub mod path;
 pub mod type_ref;
 pub mod builtin_type;
 pub mod builtin_attr;
+pub mod diagnostics;
 pub mod per_ns;
 pub mod item_scope;
 
@@ -55,6 +56,7 @@ use std::{
     sync::Arc,
 };
 
+use adt::VariantData;
 use base_db::{impl_intern_key, salsa, CrateId};
 use hir_expand::{
     ast_id_map::FileAstId,
@@ -65,18 +67,15 @@ use hir_expand::{
 use la_arena::Idx;
 use nameres::DefMap;
 use path::ModPath;
-use stdx::impl_from;
 use syntax::ast;
 
-use crate::{
-    adt::VariantData,
-    attr::AttrId,
-    builtin_type::BuiltinType,
-    item_tree::{
-        Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
-        TypeAlias, Union,
-    },
+use crate::attr::AttrId;
+use crate::builtin_type::BuiltinType;
+use item_tree::{
+    Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait,
+    TypeAlias, Union,
 };
+use stdx::impl_from;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ModuleId {
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index ebfcc26c41..249af6fc80 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -47,19 +47,18 @@
 //! path and, upon success, we run macro expansion and "collect module" phase on
 //! the result
 
-pub mod diagnostics;
 mod collector;
 mod mod_resolution;
 mod path_resolution;
-mod proc_macro;
 
 #[cfg(test)]
 mod tests;
+mod proc_macro;
 
 use std::sync::Arc;
 
 use base_db::{CrateId, Edition, FileId};
-use hir_expand::{name::Name, InFile, MacroDefId};
+use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId};
 use la_arena::Arena;
 use profile::Count;
 use rustc_hash::FxHashMap;
@@ -255,6 +254,15 @@ impl DefMap {
         }
     }
 
+    pub fn add_diagnostics(
+        &self,
+        db: &dyn DefDatabase,
+        module: LocalModuleId,
+        sink: &mut DiagnosticSink,
+    ) {
+        self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
+    }
+
     pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ {
         self.modules
             .iter()
@@ -440,11 +448,6 @@ impl DefMap {
             module.scope.shrink_to_fit();
         }
     }
-
-    /// Get a reference to the def map's diagnostics.
-    pub fn diagnostics(&self) -> &[DefDiagnostic] {
-        self.diagnostics.as_slice()
-    }
 }
 
 impl ModuleData {
@@ -468,3 +471,236 @@ pub enum ModuleSource {
     Module(ast::Module),
     BlockExpr(ast::BlockExpr),
 }
+
+mod diagnostics {
+    use cfg::{CfgExpr, CfgOptions};
+    use hir_expand::diagnostics::DiagnosticSink;
+    use hir_expand::hygiene::Hygiene;
+    use hir_expand::{InFile, MacroCallKind};
+    use syntax::ast::AttrsOwner;
+    use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr};
+
+    use crate::path::ModPath;
+    use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
+
+    #[derive(Debug, PartialEq, Eq)]
+    enum DiagnosticKind {
+        UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
+
+        UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
+
+        UnresolvedImport { ast: AstId<ast::Use>, index: usize },
+
+        UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
+
+        UnresolvedProcMacro { ast: MacroCallKind },
+
+        UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
+
+        MacroError { ast: MacroCallKind, message: String },
+    }
+
+    #[derive(Debug, PartialEq, Eq)]
+    pub(super) struct DefDiagnostic {
+        in_module: LocalModuleId,
+        kind: DiagnosticKind,
+    }
+
+    impl DefDiagnostic {
+        pub(super) fn unresolved_module(
+            container: LocalModuleId,
+            declaration: AstId<ast::Module>,
+            candidate: String,
+        ) -> Self {
+            Self {
+                in_module: container,
+                kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
+            }
+        }
+
+        pub(super) fn unresolved_extern_crate(
+            container: LocalModuleId,
+            declaration: AstId<ast::ExternCrate>,
+        ) -> Self {
+            Self {
+                in_module: container,
+                kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
+            }
+        }
+
+        pub(super) fn unresolved_import(
+            container: LocalModuleId,
+            ast: AstId<ast::Use>,
+            index: usize,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
+        }
+
+        pub(super) fn unconfigured_code(
+            container: LocalModuleId,
+            ast: AstId<ast::Item>,
+            cfg: CfgExpr,
+            opts: CfgOptions,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
+        }
+
+        pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
+        }
+
+        pub(super) fn macro_error(
+            container: LocalModuleId,
+            ast: MacroCallKind,
+            message: String,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
+        }
+
+        pub(super) fn unresolved_macro_call(
+            container: LocalModuleId,
+            ast: AstId<ast::MacroCall>,
+            path: ModPath,
+        ) -> Self {
+            Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast, path } }
+        }
+
+        pub(super) fn add_to(
+            &self,
+            db: &dyn DefDatabase,
+            target_module: LocalModuleId,
+            sink: &mut DiagnosticSink,
+        ) {
+            if self.in_module != target_module {
+                return;
+            }
+
+            match &self.kind {
+                DiagnosticKind::UnresolvedModule { declaration, candidate } => {
+                    let decl = declaration.to_node(db.upcast());
+                    sink.push(UnresolvedModule {
+                        file: declaration.file_id,
+                        decl: AstPtr::new(&decl),
+                        candidate: candidate.clone(),
+                    })
+                }
+
+                DiagnosticKind::UnresolvedExternCrate { ast } => {
+                    let item = ast.to_node(db.upcast());
+                    sink.push(UnresolvedExternCrate {
+                        file: ast.file_id,
+                        item: AstPtr::new(&item),
+                    });
+                }
+
+                DiagnosticKind::UnresolvedImport { ast, index } => {
+                    let use_item = ast.to_node(db.upcast());
+                    let hygiene = Hygiene::new(db.upcast(), ast.file_id);
+                    let mut cur = 0;
+                    let mut tree = None;
+                    ModPath::expand_use_item(
+                        db,
+                        InFile::new(ast.file_id, use_item),
+                        &hygiene,
+                        |_mod_path, use_tree, _is_glob, _alias| {
+                            if cur == *index {
+                                tree = Some(use_tree.clone());
+                            }
+
+                            cur += 1;
+                        },
+                    );
+
+                    if let Some(tree) = tree {
+                        sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
+                    }
+                }
+
+                DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
+                    let item = ast.to_node(db.upcast());
+                    sink.push(InactiveCode {
+                        file: ast.file_id,
+                        node: AstPtr::new(&item).into(),
+                        cfg: cfg.clone(),
+                        opts: opts.clone(),
+                    });
+                }
+
+                DiagnosticKind::UnresolvedProcMacro { ast } => {
+                    let mut precise_location = None;
+                    let (file, ast, name) = match ast {
+                        MacroCallKind::FnLike { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
+                        }
+                        MacroCallKind::Derive { ast_id, derive_name, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+
+                            // Compute the precise location of the macro name's token in the derive
+                            // list.
+                            // FIXME: This does not handle paths to the macro, but neither does the
+                            // rest of r-a.
+                            let derive_attrs =
+                                node.attrs().filter_map(|attr| match attr.as_simple_call() {
+                                    Some((name, args)) if name == "derive" => Some(args),
+                                    _ => None,
+                                });
+                            'outer: for attr in derive_attrs {
+                                let tokens =
+                                    attr.syntax().children_with_tokens().filter_map(|elem| {
+                                        match elem {
+                                            syntax::NodeOrToken::Node(_) => None,
+                                            syntax::NodeOrToken::Token(tok) => Some(tok),
+                                        }
+                                    });
+                                for token in tokens {
+                                    if token.kind() == SyntaxKind::IDENT
+                                        && token.text() == derive_name.as_str()
+                                    {
+                                        precise_location = Some(token.text_range());
+                                        break 'outer;
+                                    }
+                                }
+                            }
+
+                            (
+                                ast_id.file_id,
+                                SyntaxNodePtr::from(AstPtr::new(&node)),
+                                Some(derive_name.clone()),
+                            )
+                        }
+                    };
+                    sink.push(UnresolvedProcMacro {
+                        file,
+                        node: ast,
+                        precise_location,
+                        macro_name: name,
+                    });
+                }
+
+                DiagnosticKind::UnresolvedMacroCall { ast, path } => {
+                    let node = ast.to_node(db.upcast());
+                    sink.push(UnresolvedMacroCall {
+                        file: ast.file_id,
+                        node: AstPtr::new(&node),
+                        path: path.clone(),
+                    });
+                }
+
+                DiagnosticKind::MacroError { ast, message } => {
+                    let (file, ast) = match ast {
+                        MacroCallKind::FnLike { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
+                        }
+                        MacroCallKind::Derive { ast_id, .. } => {
+                            let node = ast_id.to_node(db.upcast());
+                            (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
+                        }
+                    };
+                    sink.push(MacroError { file, node: ast, message: message.clone() });
+                }
+            }
+        }
+    }
+}
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 3ea4729080..2ae740d0e6 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -33,10 +33,7 @@ use crate::{
     },
     macro_call_as_call_id,
     nameres::{
-        diagnostics::DefDiagnostic,
-        mod_resolution::ModDir,
-        path_resolution::ReachedFixedPoint,
-        proc_macro::{ProcMacroDef, ProcMacroKind},
+        diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint,
         BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode,
     },
     path::{ImportAlias, ModPath, PathKind},
@@ -47,6 +44,8 @@ use crate::{
     UnresolvedMacro,
 };
 
+use super::proc_macro::{ProcMacroDef, ProcMacroKind};
+
 const GLOB_RECURSION_LIMIT: usize = 100;
 const EXPANSION_DEPTH_LIMIT: usize = 128;
 const FIXED_POINT_LIMIT: usize = 8192;
diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs
deleted file mode 100644
index 8f2f0ff9f5..0000000000
--- a/crates/hir_def/src/nameres/diagnostics.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-//! Diagnostics emitted during DefMap construction.
-
-use cfg::{CfgExpr, CfgOptions};
-use hir_expand::MacroCallKind;
-use syntax::ast;
-
-use crate::{nameres::LocalModuleId, path::ModPath, AstId};
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum DefDiagnosticKind {
-    UnresolvedModule { ast: AstId<ast::Module>, candidate: String },
-
-    UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
-
-    UnresolvedImport { ast: AstId<ast::Use>, index: usize },
-
-    UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
-
-    UnresolvedProcMacro { ast: MacroCallKind },
-
-    UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
-
-    MacroError { ast: MacroCallKind, message: String },
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct DefDiagnostic {
-    pub in_module: LocalModuleId,
-    pub kind: DefDiagnosticKind,
-}
-
-impl DefDiagnostic {
-    pub(super) fn unresolved_module(
-        container: LocalModuleId,
-        declaration: AstId<ast::Module>,
-        candidate: String,
-    ) -> Self {
-        Self {
-            in_module: container,
-            kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate },
-        }
-    }
-
-    pub(super) fn unresolved_extern_crate(
-        container: LocalModuleId,
-        declaration: AstId<ast::ExternCrate>,
-    ) -> Self {
-        Self {
-            in_module: container,
-            kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
-        }
-    }
-
-    pub(super) fn unresolved_import(
-        container: LocalModuleId,
-        ast: AstId<ast::Use>,
-        index: usize,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
-    }
-
-    pub(super) fn unconfigured_code(
-        container: LocalModuleId,
-        ast: AstId<ast::Item>,
-        cfg: CfgExpr,
-        opts: CfgOptions,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
-    }
-
-    pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast } }
-    }
-
-    pub(super) fn macro_error(
-        container: LocalModuleId,
-        ast: MacroCallKind,
-        message: String,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
-    }
-
-    pub(super) fn unresolved_macro_call(
-        container: LocalModuleId,
-        ast: AstId<ast::MacroCall>,
-        path: ModPath,
-    ) -> Self {
-        Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
-    }
-}
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index d9ec03d2da..45ab9d0ffb 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -71,7 +71,7 @@ impl ModPath {
     }
 
     /// Calls `cb` with all paths, represented by this use item.
-    pub fn expand_use_item(
+    pub(crate) fn expand_use_item(
         db: &dyn DefDatabase,
         item_src: InFile<ast::Use>,
         hygiene: &Hygiene,
diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs
index e8f4af3095..625c26f0a0 100644
--- a/crates/hir_expand/src/db.rs
+++ b/crates/hir_expand/src/db.rs
@@ -186,7 +186,7 @@ fn parse_macro_expansion(
         // The final goal we would like to make all parse_macro success,
         // such that the following log will not call anyway.
         let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
-        let node = loc.kind.to_node(db);
+        let node = loc.kind.node(db);
 
         // collect parent information for warning log
         let parents =
diff --git a/crates/hir_ty/src/diagnostics_sink.rs b/crates/hir_expand/src/diagnostics.rs
similarity index 99%
rename from crates/hir_ty/src/diagnostics_sink.rs
rename to crates/hir_expand/src/diagnostics.rs
index 084fa8b06a..bf0b85ce9d 100644
--- a/crates/hir_ty/src/diagnostics_sink.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -16,9 +16,10 @@
 
 use std::{any::Any, fmt};
 
-use hir_expand::InFile;
 use syntax::SyntaxNodePtr;
 
+use crate::InFile;
+
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub struct DiagnosticCode(pub &'static str);
 
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 10d37234e7..6be4516a32 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -8,6 +8,7 @@ pub mod db;
 pub mod ast_id_map;
 pub mod name;
 pub mod hygiene;
+pub mod diagnostics;
 pub mod builtin_derive;
 pub mod builtin_macro;
 pub mod proc_macro;
@@ -107,7 +108,7 @@ impl HirFileId {
             HirFileIdRepr::FileId(_) => None,
             HirFileIdRepr::MacroFile(macro_file) => {
                 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
-                Some(loc.kind.to_node(db))
+                Some(loc.kind.node(db))
             }
         }
     }
@@ -152,7 +153,7 @@ impl HirFileId {
             HirFileIdRepr::MacroFile(macro_file) => {
                 let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
                 let item = match loc.def.kind {
-                    MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
+                    MacroDefKind::BuiltInDerive(..) => loc.kind.node(db),
                     _ => return None,
                 };
                 Some(item.with_value(ast::Item::cast(item.value.clone())?))
@@ -268,7 +269,7 @@ impl MacroCallKind {
         }
     }
 
-    pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
+    fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
         match self {
             MacroCallKind::FnLike { ast_id, .. } => {
                 ast_id.with_value(ast_id.to_node(db).syntax().clone())
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 7598e2193a..84fc8ce14d 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -8,14 +8,12 @@ use std::{any::Any, fmt};
 
 use base_db::CrateId;
 use hir_def::{DefWithBodyId, ModuleDefId};
+use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
 use hir_expand::{name::Name, HirFileId, InFile};
 use stdx::format_to;
 use syntax::{ast, AstPtr, SyntaxNodePtr};
 
-use crate::{
-    db::HirDatabase,
-    diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink},
-};
+use crate::db::HirDatabase;
 
 pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
 
@@ -448,13 +446,15 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap {
 mod tests {
     use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
     use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId};
-    use hir_expand::db::AstDatabase;
+    use hir_expand::{
+        db::AstDatabase,
+        diagnostics::{Diagnostic, DiagnosticSinkBuilder},
+    };
     use rustc_hash::FxHashMap;
     use syntax::{TextRange, TextSize};
 
     use crate::{
         diagnostics::{validate_body, validate_module_item},
-        diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder},
         test_db::TestDB,
     };
 
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
index ef982cbcd4..075dc4131f 100644
--- a/crates/hir_ty/src/diagnostics/decl_check.rs
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -19,7 +19,10 @@ use hir_def::{
     src::HasSource,
     AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
 };
-use hir_expand::name::{AsName, Name};
+use hir_expand::{
+    diagnostics::DiagnosticSink,
+    name::{AsName, Name},
+};
 use stdx::{always, never};
 use syntax::{
     ast::{self, NameOwner},
@@ -29,7 +32,6 @@ use syntax::{
 use crate::{
     db::HirDatabase,
     diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase},
-    diagnostics_sink::DiagnosticSink,
 };
 
 mod allow {
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 86f82e3fa4..d1f113e7ff 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -5,7 +5,7 @@
 use std::sync::Arc;
 
 use hir_def::{expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId};
-use hir_expand::name;
+use hir_expand::{diagnostics::DiagnosticSink, name};
 use rustc_hash::FxHashSet;
 use syntax::{ast, AstPtr};
 
@@ -16,7 +16,6 @@ use crate::{
         MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
         MissingPatFields, RemoveThisSemicolon,
     },
-    diagnostics_sink::DiagnosticSink,
     AdtId, InferenceResult, Interner, TyExt, TyKind,
 };
 
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index c3c483425e..5d13bddea3 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -9,10 +9,10 @@ use hir_def::{
     resolver::{resolver_for_expr, ResolveValueResult, ValueNs},
     DefWithBodyId,
 };
+use hir_expand::diagnostics::DiagnosticSink;
 
 use crate::{
-    db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult,
-    Interner, TyExt, TyKind,
+    db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Interner, TyExt, TyKind,
 };
 
 pub(super) struct UnsafeValidator<'a, 'b: 'a> {
diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs
index 8cefd80f3a..164e850504 100644
--- a/crates/hir_ty/src/infer.rs
+++ b/crates/hir_ty/src/infer.rs
@@ -28,14 +28,13 @@ use hir_def::{
     AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup,
     TraitId, TypeAliasId, VariantId,
 };
-use hir_expand::name::name;
+use hir_expand::{diagnostics::DiagnosticSink, name::name};
 use la_arena::ArenaMap;
 use rustc_hash::FxHashMap;
 use stdx::impl_from;
 use syntax::SmolStr;
 
 use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty};
-use crate::diagnostics_sink::DiagnosticSink;
 use crate::{
     db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic,
     lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution,
@@ -799,11 +798,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 50e0d63333..ef021978ad 100644
--- a/crates/hir_ty/src/lib.rs
+++ b/crates/hir_ty/src/lib.rs
@@ -21,7 +21,6 @@ mod utils;
 mod walk;
 pub mod db;
 pub mod diagnostics;
-pub mod diagnostics_sink;
 pub mod display;
 pub mod method_resolution;
 pub mod primitive;
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs
index 5ebe7fd0ea..b0c4ed60aa 100644
--- a/crates/ide/src/annotations.rs
+++ b/crates/ide/src/annotations.rs
@@ -5,7 +5,7 @@ use ide_db::{
     helpers::visit_file_defs,
     RootDatabase,
 };
-use syntax::{ast::NameOwner, AstNode, TextRange, TextSize};
+use syntax::{ast::NameOwner, AstNode, TextRange};
 
 use crate::{
     fn_references::find_all_methods,
@@ -80,26 +80,26 @@ pub(crate) fn annotations(
 
     visit_file_defs(&Semantics::new(db), file_id, &mut |def| match def {
         Either::Left(def) => {
-            let node = match def {
+            let range = match def {
                 hir::ModuleDef::Const(konst) => {
-                    konst.source(db).and_then(|node| range_and_position_of(&node, file_id))
+                    konst.source(db).and_then(|node| name_range(&node, file_id))
                 }
                 hir::ModuleDef::Trait(trait_) => {
-                    trait_.source(db).and_then(|node| range_and_position_of(&node, file_id))
+                    trait_.source(db).and_then(|node| name_range(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
-                    strukt.source(db).and_then(|node| range_and_position_of(&node, file_id))
+                    strukt.source(db).and_then(|node| name_range(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
-                    enum_.source(db).and_then(|node| range_and_position_of(&node, file_id))
+                    enum_.source(db).and_then(|node| name_range(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
-                    union.source(db).and_then(|node| range_and_position_of(&node, file_id))
+                    union.source(db).and_then(|node| name_range(&node, file_id))
                 }
                 _ => None,
             };
-            let (offset, range) = match node {
-                Some(node) => node,
+            let (range, offset) = match range {
+                Some(range) => (range, range.start()),
                 None => return,
             };
 
@@ -122,18 +122,12 @@ pub(crate) fn annotations(
                 });
             }
 
-            fn range_and_position_of<T: NameOwner>(
-                node: &InFile<T>,
-                file_id: FileId,
-            ) -> Option<(TextSize, TextRange)> {
-                if node.file_id != file_id.into() {
+            fn name_range<T: NameOwner>(node: &InFile<T>, file_id: FileId) -> Option<TextRange> {
+                if node.file_id == file_id.into() {
+                    node.value.name().map(|it| it.syntax().text_range())
+                } else {
                     // Node is outside the file we are adding annotations to (e.g. macros).
                     None
-                } else {
-                    Some((
-                        node.value.name()?.syntax().text_range().start(),
-                        node.value.syntax().text_range(),
-                    ))
                 }
             }
         }
@@ -141,13 +135,15 @@ pub(crate) fn annotations(
     });
 
     if config.annotate_method_references {
-        annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation {
-            range: method.range,
-            kind: AnnotationKind::HasReferences {
-                position: FilePosition { file_id, offset: method.range.start() },
-                data: None,
+        annotations.extend(find_all_methods(db, file_id).into_iter().map(
+            |FileRange { file_id, range }| Annotation {
+                range,
+                kind: AnnotationKind::HasReferences {
+                    position: FilePosition { file_id, offset: range.start() },
+                    data: None,
+                },
             },
-        }));
+        ));
     }
 
     annotations
@@ -266,7 +262,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..22,
+                        range: 6..10,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -287,7 +283,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 24..48,
+                        range: 30..36,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -370,7 +366,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -384,7 +380,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -478,7 +474,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -502,7 +498,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -529,7 +525,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 14..34,
+                        range: 20..31,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -553,7 +549,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 14..34,
+                        range: 20..31,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -712,7 +708,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -736,7 +732,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 0..12,
+                        range: 7..11,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dcac7c76d2..27d347dbd9 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -299,10 +299,10 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
 
 #[cfg(test)]
 mod tests {
-    use expect_test::Expect;
+    use expect_test::{expect, Expect};
     use ide_assists::AssistResolveStrategy;
     use stdx::trim_indent;
-    use test_utils::{assert_eq_text, extract_annotations};
+    use test_utils::assert_eq_text;
 
     use crate::{fixture, DiagnosticsConfig};
 
@@ -396,51 +396,26 @@ mod tests {
         expect.assert_debug_eq(&diagnostics)
     }
 
-    pub(crate) fn check_diagnostics(ra_fixture: &str) {
-        let (analysis, file_id) = fixture::file(ra_fixture);
-        let diagnostics = analysis
-            .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
-            .unwrap();
-
-        let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
-        let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>();
-        assert_eq!(expected, actual);
-    }
-
     #[test]
     fn test_unresolved_macro_range() {
-        check_diagnostics(
-            r#"
-foo::bar!(92);
-   //^^^ unresolved macro `foo::bar!`
-"#,
-        );
-    }
-
-    #[test]
-    fn unresolved_import_in_use_tree() {
-        // Only the relevant part of a nested `use` item should be highlighted.
-        check_diagnostics(
-            r#"
-use does_exist::{Exists, DoesntExist};
-                       //^^^^^^^^^^^ unresolved import
-
-use {does_not_exist::*, does_exist};
-   //^^^^^^^^^^^^^^^^^ unresolved import
-
-use does_not_exist::{
-    a,
-  //^ unresolved import
-    b,
-  //^ unresolved import
-    c,
-  //^ unresolved import
-};
-
-mod does_exist {
-    pub struct Exists;
-}
-"#,
+        check_expect(
+            r#"foo::bar!(92);"#,
+            expect![[r#"
+                [
+                    Diagnostic {
+                        message: "unresolved macro `foo::bar!`",
+                        range: 5..8,
+                        severity: Error,
+                        fixes: None,
+                        unused: false,
+                        code: Some(
+                            DiagnosticCode(
+                                "unresolved-macro-call",
+                            ),
+                        ),
+                    },
+                ]
+            "#]],
         );
     }
 
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 9503c936d1..b4a3d39c91 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -71,7 +71,7 @@ pub(super) fn element(
         }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
-            highlight_func_by_name_ref(sema, krate, &name_ref).unwrap_or_else(|| {
+            highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
                 let is_self = name_ref.self_token().is_some();
                 let h = match NameRefClass::classify(sema, &name_ref) {
                     Some(name_kind) => match name_kind {
@@ -108,7 +108,7 @@ pub(super) fn element(
                         NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
                     },
                     None if syntactic_name_ref_highlighting => {
-                        highlight_name_ref_by_syntax(name_ref, sema, krate)
+                        highlight_name_ref_by_syntax(name_ref, sema)
                     }
                     None => HlTag::UnresolvedReference.into(),
                 };
@@ -434,23 +434,19 @@ fn highlight_def(db: &RootDatabase, krate: Option<hir::Crate>, def: Definition)
 
 fn highlight_func_by_name_ref(
     sema: &Semantics<RootDatabase>,
-    krate: Option<hir::Crate>,
     name_ref: &ast::NameRef,
 ) -> Option<Highlight> {
     let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
-    highlight_method_call(sema, krate, &mc)
+    highlight_method_call(sema, &mc)
 }
 
 fn highlight_method_call(
     sema: &Semantics<RootDatabase>,
-    krate: Option<hir::Crate>,
     method_call: &ast::MethodCallExpr,
 ) -> Option<Highlight> {
     let func = sema.resolve_method_call(&method_call)?;
-
     let mut h = SymbolKind::Function.into();
     h |= HlMod::Associated;
-
     if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
         h |= HlMod::Unsafe;
     }
@@ -458,10 +454,7 @@ fn highlight_method_call(
         h |= HlMod::Async;
     }
     if func.as_assoc_item(sema.db).and_then(|it| it.containing_trait(sema.db)).is_some() {
-        h |= HlMod::Trait;
-    }
-    if Some(func.module(sema.db).krate()) != krate {
-        h |= HlMod::Library;
+        h |= HlMod::Trait
     }
 
     if let Some(self_param) = func.self_param(sema.db) {
@@ -510,11 +503,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
     tag.into()
 }
 
-fn highlight_name_ref_by_syntax(
-    name: ast::NameRef,
-    sema: &Semantics<RootDatabase>,
-    krate: Option<hir::Crate>,
-) -> Highlight {
+fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
     let default = HlTag::UnresolvedReference;
 
     let parent = match name.syntax().parent() {
@@ -525,7 +514,7 @@ fn highlight_name_ref_by_syntax(
     match parent.kind() {
         METHOD_CALL_EXPR => {
             return ast::MethodCallExpr::cast(parent)
-                .and_then(|it| highlight_method_call(sema, krate, &it))
+                .and_then(|it| highlight_method_call(sema, &it))
                 .unwrap_or_else(|| SymbolKind::Function.into());
         }
         FIELD_EXPR => {
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs
index 40006fb74f..2f95b86874 100644
--- a/crates/ide_completion/src/completions/record.rs
+++ b/crates/ide_completion/src/completions/record.rs
@@ -108,6 +108,8 @@ fn process(f: S) {
         check_snippet(
             test_code,
             expect![[r#"
+                sn pd
+                sn ppd
                 fd ..Default::default()
             "#]],
         );
@@ -177,7 +179,13 @@ fn process(f: S) {
             "#]],
         );
 
-        check_snippet(test_code, expect![[r#""#]]);
+        check_snippet(
+            test_code,
+            expect![[r#"
+                sn pd
+                sn ppd
+            "#]],
+        );
     }
 
     #[test]
diff --git a/crates/ide_completion/src/completions/snippet.rs b/crates/ide_completion/src/completions/snippet.rs
index 14cfb61dee..7f7830976b 100644
--- a/crates/ide_completion/src/completions/snippet.rs
+++ b/crates/ide_completion/src/completions/snippet.rs
@@ -22,10 +22,8 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
         None => return,
     };
 
-    if ctx.can_be_stmt {
-        snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
-        snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
-    }
+    snippet(ctx, cap, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc);
+    snippet(ctx, cap, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc);
 }
 
 pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 93b5ff55f7..7a53e4a8b9 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -455,11 +455,7 @@ impl ProjectFolders {
                 dirs.include.extend(root.include);
                 dirs.exclude.extend(root.exclude);
                 for excl in global_excludes {
-                    if dirs
-                        .include
-                        .iter()
-                        .any(|incl| incl.starts_with(excl) || excl.starts_with(incl))
-                    {
+                    if dirs.include.iter().any(|incl| incl.starts_with(excl)) {
                         dirs.exclude.push(excl.clone());
                     }
                 }
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index d0be840686..138e3f6863 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -142,11 +142,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
     // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
     const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
     const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
-
     cargoTask.presentationOptions.clear = true;
-    // Sadly, this doesn't prevent focus stealing if the terminal is currently
-    // hidden, and will become revealed due to task exucution.
-    cargoTask.presentationOptions.focus = false;
 
     return cargoTask;
 }


Test patch:
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index d4fae05a69..3e8f16306b 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -96,26 +96,26 @@ fn f() {
     // The three g̶e̶n̶d̶e̶r̶s̶ statements:
 
     #[cfg(a)] fn f() {}  // Item statement
-  //^^^^^^^^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     #[cfg(a)] {}         // Expression statement
-  //^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     #[cfg(a)] let x = 0; // let statement
-  //^^^^^^^^^^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
 
     abc(#[cfg(a)] 0);
-      //^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     let x = Struct {
         #[cfg(a)] f: 0,
-      //^^^^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     };
     match () {
         () => (),
         #[cfg(a)] () => (),
-      //^^^^^^^^^^^^^^^^^^ InactiveCode
+      //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
     }
 
     #[cfg(a)] 0          // Trailing expression of block
-  //^^^^^^^^^^^ InactiveCode
+  //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
 }
     ",
     );
@@ -188,7 +188,7 @@ fn unresolved_macro_diag() {
         r#"
 fn f() {
     m!();
-  //^^^^ UnresolvedMacroCall
+  //^^^^ unresolved macro `m!`
 }
       "#,
     );
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index ec66709528..75147d973a 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -18,13 +18,40 @@ fn unresolved_import() {
         r"
         use does_exist;
         use does_not_exist;
-      //^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+          //^^^^^^^^^^^^^^ unresolved import
 
         mod does_exist {}
         ",
     );
 }
 
+#[test]
+fn unresolved_import_in_use_tree() {
+    // Only the relevant part of a nested `use` item should be highlighted.
+    check_diagnostics(
+        r"
+        use does_exist::{Exists, DoesntExist};
+                               //^^^^^^^^^^^ unresolved import
+
+        use {does_not_exist::*, does_exist};
+           //^^^^^^^^^^^^^^^^^ unresolved import
+
+        use does_not_exist::{
+            a,
+          //^ unresolved import
+            b,
+          //^ unresolved import
+            c,
+          //^ unresolved import
+        };
+
+        mod does_exist {
+            pub struct Exists;
+        }
+        ",
+    );
+}
+
 #[test]
 fn unresolved_extern_crate() {
     check_diagnostics(
@@ -32,7 +59,7 @@ fn unresolved_extern_crate() {
         //- /main.rs crate:main deps:core
         extern crate core;
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
         //- /lib.rs crate:core
         ",
     );
@@ -45,7 +72,7 @@ fn extern_crate_self_as() {
         r"
         //- /lib.rs
           extern crate doesnotexist;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
         // Should not error.
         extern crate self as foo;
         struct Foo;
@@ -61,18 +88,18 @@ fn dedup_unresolved_import_from_unresolved_crate() {
         //- /main.rs crate:main
         mod a {
             extern crate doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
+          //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
 
             // Should not error, since we already errored for the missing crate.
             use doesnotexist::{self, bla, *};
 
             use crate::doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+              //^^^^^^^^^^^^^^^^^^^ unresolved import
         }
 
         mod m {
             use super::doesnotexist;
-          //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
+              //^^^^^^^^^^^^^^^^^^^ unresolved import
         }
         ",
     );
@@ -85,7 +112,7 @@ fn unresolved_module() {
         //- /lib.rs
         mod foo;
           mod bar;
-        //^^^^^^^^ UnresolvedModule
+        //^^^^^^^^ unresolved module
         mod baz {}
         //- /foo.rs
         ",
@@ -100,16 +127,16 @@ fn inactive_item() {
         r#"
         //- /lib.rs
           #[cfg(no)] pub fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
 
           #[cfg(no)] #[cfg(no2)] mod m;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
 
           #[cfg(all(not(a), b))] enum E {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
 
           #[cfg(feature = "std")] use std;
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
         "#,
     );
 }
@@ -122,14 +149,14 @@ fn inactive_via_cfg_attr() {
         r#"
         //- /lib.rs
           #[cfg_attr(not(never), cfg(no))] fn f() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
 
           #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
 
           #[cfg_attr(never, cfg(no))] fn g() {}
 
           #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
-        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
+        //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
         "#,
     );
 }
@@ -143,7 +170,7 @@ fn unresolved_legacy_scope_macro() {
 
           m!();
           m2!();
-        //^^^^^^ UnresolvedMacroCall
+        //^^^^^^ unresolved macro `self::m2!`
         "#,
     );
 }
@@ -160,7 +187,7 @@ fn unresolved_module_scope_macro() {
 
           self::m!();
           self::m2!();
-        //^^^^^^^^^^^^ UnresolvedMacroCall
+        //^^^^^^^^^^^^ unresolved macro `self::m2!`
         "#,
     );
 }
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index 6c357c915b..8fa703a574 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -5,20 +5,19 @@ use std::{
     sync::{Arc, Mutex},
 };
 
-use base_db::{
-    salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
-};
+use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast};
 use base_db::{AnchoredPath, SourceDatabase};
+use hir_expand::diagnostics::Diagnostic;
+use hir_expand::diagnostics::DiagnosticSinkBuilder;
 use hir_expand::{db::AstDatabase, InFile};
 use rustc_hash::FxHashMap;
 use rustc_hash::FxHashSet;
-use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
+use syntax::{algo, ast, AstNode, TextRange, TextSize};
 use test_utils::extract_annotations;
 
 use crate::{
-    body::BodyDiagnostic,
     db::DefDatabase,
-    nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
+    nameres::{DefMap, ModuleSource},
     src::HasSource,
     LocalModuleId, Lookup, ModuleDefId, ModuleId,
 };
@@ -263,70 +262,19 @@ impl TestDB {
             .collect()
     }
 
-    pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
+    pub(crate) fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
         let crate_graph = self.crate_graph();
         for krate in crate_graph.iter() {
             let crate_def_map = self.crate_def_map(krate);
 
-            for diag in crate_def_map.diagnostics() {
-                let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
-                    DefDiagnosticKind::UnresolvedModule { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
-                    }
-                    DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
-                    }
-                    DefDiagnosticKind::UnresolvedImport { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
-                    }
-                    DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
-                    }
-                    DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
-                        (ast.to_node(self.upcast()), "UnresolvedProcMacro")
-                    }
-                    DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
-                        let node = ast.to_node(self.upcast());
-                        (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
-                    }
-                    DefDiagnosticKind::MacroError { ast, message } => {
-                        (ast.to_node(self.upcast()), message.as_str())
-                    }
-                };
-
-                let frange = node.as_ref().original_file_range(self);
-                cb(frange, message.to_string())
-            }
+            let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
+            for (module_id, module) in crate_def_map.modules() {
+                crate_def_map.add_diagnostics(self, module_id, &mut sink);
 
-            for (_module_id, module) in crate_def_map.modules() {
                 for decl in module.scope.declarations() {
                     if let ModuleDefId::FunctionId(it) = decl {
                         let source_map = self.body_with_source_map(it.into()).1;
-                        for diag in source_map.diagnostics() {
-                            let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
-                                BodyDiagnostic::InactiveCode { node, .. } => {
-                                    (node.clone().map(|it| it.into()), "InactiveCode")
-                                }
-                                BodyDiagnostic::MacroError { node, message } => {
-                                    (node.clone().map(|it| it.into()), message.as_str())
-                                }
-                                BodyDiagnostic::UnresolvedProcMacro { node } => {
-                                    (node.clone().map(|it| it.into()), "UnresolvedProcMacro")
-                                }
-                                BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
-                                    (node.clone().map(|it| it.into()), "UnresolvedMacroCall")
-                                }
-                            };
-
-                            let root = self.parse_or_expand(ptr.file_id).unwrap();
-                            let node = ptr.map(|ptr| ptr.to_node(&root));
-                            let frange = node.as_ref().original_file_range(self);
-                            cb(frange, message.to_string())
-                        }
+                        source_map.add_diagnostics(self, &mut sink);
                     }
                 }
             }
@@ -339,7 +287,14 @@ impl TestDB {
         assert!(!annotations.is_empty());
 
         let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
-        db.diagnostics(&mut |frange, message| {
+        db.diagnostics(|d| {
+            let src = d.display_source();
+            let root = db.parse_or_expand(src.file_id).unwrap();
+
+            let node = src.map(|ptr| ptr.to_node(&root));
+            let frange = node.as_ref().original_file_range(db);
+
+            let message = d.message();
             actual.entry(frange.file_id).or_default().push((frange.range, message));
         });
 
@@ -364,7 +319,7 @@ impl TestDB {
         assert!(annotations.is_empty());
 
         let mut has_diagnostics = false;
-        db.diagnostics(&mut |_, _| {
+        db.diagnostics(|_| {
             has_diagnostics = true;
         });
 
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 0264e39a33..055d211090 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -258,7 +258,7 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
     <span class="keyword">let</span> <span class="variable declaration">control_flow</span> <span class="operator">=</span> <span class="module library">foo</span><span class="operator">::</span><span class="function library">identity</span><span class="parenthesis">(</span><span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Continue</span><span class="parenthesis">)</span><span class="semicolon">;</span>
 
-    <span class="keyword control">if</span> <span class="variable">control_flow</span><span class="operator">.</span><span class="function associated consuming library">should_die</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
+    <span class="keyword control">if</span> <span class="keyword">let</span> <span class="module library">foo</span><span class="operator">::</span><span class="enum library">ControlFlow</span><span class="operator">::</span><span class="enum_variant library">Die</span> <span class="operator">=</span> <span class="variable">control_flow</span> <span class="brace">{</span>
         foo::<span class="macro">die!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
     <span class="brace">}</span>
 <span class="brace">}</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 662b534813..be4447ebbf 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -232,7 +232,7 @@ fn use_foo_items() {
 
     let control_flow = foo::identity(foo::ControlFlow::Continue);
 
-    if control_flow.should_die() {
+    if let foo::ControlFlow::Die = control_flow {
         foo::die!();
     }
 }
@@ -249,12 +249,6 @@ pub enum ControlFlow {
     Die,
 }
 
-impl ControlFlow {
-    pub fn should_die(self) -> bool {
-        matches!(self, ControlFlow::Die)
-    }
-}
-
 pub fn identity<T>(x: T) -> T { x }
 
 pub mod consts {