Repository: rust-analyzer/rust-analyzer
Pull request #9003 (2021-05-26 11:38:27)

minor: Document semantic token modifiers
Part of #6457


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 b0c4ed60aa..5ebe7fd0ea 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};
+use syntax::{ast::NameOwner, AstNode, TextRange, TextSize};
 
 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 range = match def {
+            let node = match def {
                 hir::ModuleDef::Const(konst) => {
-                    konst.source(db).and_then(|node| name_range(&node, file_id))
+                    konst.source(db).and_then(|node| range_and_position_of(&node, file_id))
                 }
                 hir::ModuleDef::Trait(trait_) => {
-                    trait_.source(db).and_then(|node| name_range(&node, file_id))
+                    trait_.source(db).and_then(|node| range_and_position_of(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
-                    strukt.source(db).and_then(|node| name_range(&node, file_id))
+                    strukt.source(db).and_then(|node| range_and_position_of(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Enum(enum_)) => {
-                    enum_.source(db).and_then(|node| name_range(&node, file_id))
+                    enum_.source(db).and_then(|node| range_and_position_of(&node, file_id))
                 }
                 hir::ModuleDef::Adt(hir::Adt::Union(union)) => {
-                    union.source(db).and_then(|node| name_range(&node, file_id))
+                    union.source(db).and_then(|node| range_and_position_of(&node, file_id))
                 }
                 _ => None,
             };
-            let (range, offset) = match range {
-                Some(range) => (range, range.start()),
+            let (offset, range) = match node {
+                Some(node) => node,
                 None => return,
             };
 
@@ -122,12 +122,18 @@ pub(crate) fn annotations(
                 });
             }
 
-            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 {
+            fn range_and_position_of<T: NameOwner>(
+                node: &InFile<T>,
+                file_id: FileId,
+            ) -> Option<(TextSize, TextRange)> {
+                if node.file_id != file_id.into() {
                     // 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(),
+                    ))
                 }
             }
         }
@@ -135,15 +141,13 @@ pub(crate) fn annotations(
     });
 
     if config.annotate_method_references {
-        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.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
@@ -262,7 +266,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 6..10,
+                        range: 0..22,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -283,7 +287,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 30..36,
+                        range: 24..48,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -366,7 +370,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -380,7 +384,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -474,7 +478,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -498,7 +502,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -525,7 +529,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 20..31,
+                        range: 14..34,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -549,7 +553,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 20..31,
+                        range: 14..34,
                         kind: HasReferences {
                             position: FilePosition {
                                 file_id: FileId(
@@ -708,7 +712,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         kind: HasImpls {
                             position: FilePosition {
                                 file_id: FileId(
@@ -732,7 +736,7 @@ fn main() {
                         },
                     },
                     Annotation {
-                        range: 7..11,
+                        range: 0..12,
                         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.rs b/crates/ide/src/syntax_highlighting.rs
index cf1a8bad79..7f7f4d38af 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -49,6 +49,30 @@ pub struct HlRange {
 // The general rule is that a reference to an entity gets colored the same way as the entity itself.
 // We also give special modifier for `mut` and `&mut` local variables.
 //
+// .Token Modifiers
+//
+// Token modifiers allow to style some elements in the source code more precisely.
+//
+// Rust-analyzer currently emits the following token modifiers:
+//
+// [horizontal]
+// associated:: Emitted for associated items.
+// async:: Emitted for async functions and the `async` and `await` keywords.
+// attribute:: Emitted for tokens inside attributes.
+// callable:: Emitted for locals whose types implements one of the `Fn*` traits.
+// consuming:: Emitted for locals that are being consumed when use in a function call.
+// controlFlow:: Emitted for control-flow related tokens, this includes the `?` operator.
+// declaration:: Emitted for names of definitions, like `foo` in `fn foo() {}`.
+// documentation:: Emitted for documentation comments.
+// injected:: Emitted for doc-string injected highlighting like rust source blocks in documentation.
+// intraDocLink:: Emitted for intra doc links in doc-strings.
+// library:: Emitted for items that are defined outside of the current crate.
+// mutable:: Emitted for mutable locals and statics.
+// static:: Emitted for "static" functions, also known as functions that do not take a `self` param.
+// trait:: Emitted for associated trait items.
+// unsafe:: Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.
+//
+//
 // image::https://user-images.githubusercontent.com/48062697/113164457-06cfb980-9239-11eb-819b-0f93e646acf8.png[]
 // image::https://user-images.githubusercontent.com/48062697/113187625-f7f50100-9250-11eb-825e-91c58f236071.png[]
 pub(crate) fn highlight(
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/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index e94f17cd98..9d481deae3 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -37,6 +37,8 @@ pub enum HlTag {
     None,
 }
 
+// Don't forget to adjust the feature description in crates/ide/src/syntax_highlighting.rs.
+// And make sure to use the lsp strings used when converting to the protocol in crates\rust-analyzer\src\semantic_tokens.rs, not the names of the variants here.
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
 #[repr(u8)]
 pub enum HlMod {
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 {