From 5c9f31d4c28478b4373e6cf5ec155745c840ee3f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 23 May 2021 23:31:59 +0300 Subject: internal: move diagnostics to hir The idea here is to eventually get rid of `dyn Diagnostic` and `DiagnosticSink` infrastructure altogether, and just have a `enum hir::Diagnostic` instead. The problem with `dyn Diagnostic` is that it is defined in the lowest level of the stack (hir_expand), but is used by the highest level (ide). As a first step, we free hir_expand and hir_def from `dyn Diagnostic` and kick the can up to `hir_ty`, as an intermediate state. The plan is then to move DiagnosticSink similarly to the hir crate, and, as final third step, remove its usage from the ide. One currently unsolved problem is testing. You can notice that the test which checks precise diagnostic ranges, unresolved_import_in_use_tree, was moved to the ide layer. Logically, only IDE should have the infra to render a specific range. At the same time, the range is determined with the data produced in hir_def and hir crates, so this layering is rather unfortunate. Working on hir_def shouldn't require compiling `ide` for testing. --- crates/hir/src/diagnostics.rs | 232 +++++++++++++++++++++- crates/hir/src/lib.rs | 176 ++++++++++++++++- crates/hir_def/src/body.rs | 25 ++- crates/hir_def/src/body/diagnostics.rs | 32 --- crates/hir_def/src/body/lower.rs | 45 ++--- crates/hir_def/src/body/tests.rs | 16 +- crates/hir_def/src/diagnostics.rs | 227 --------------------- crates/hir_def/src/lib.rs | 17 +- crates/hir_def/src/nameres.rs | 252 +----------------------- crates/hir_def/src/nameres/collector.rs | 7 +- crates/hir_def/src/nameres/diagnostics.rs | 90 +++++++++ crates/hir_def/src/nameres/tests/diagnostics.rs | 57 ++---- crates/hir_def/src/path.rs | 2 +- crates/hir_def/src/test_db.rs | 83 ++++++-- crates/hir_expand/src/db.rs | 2 +- crates/hir_expand/src/diagnostics.rs | 110 ----------- crates/hir_expand/src/lib.rs | 7 +- crates/hir_ty/src/diagnostics.rs | 12 +- crates/hir_ty/src/diagnostics/decl_check.rs | 6 +- crates/hir_ty/src/diagnostics/expr.rs | 3 +- crates/hir_ty/src/diagnostics/unsafe_check.rs | 4 +- crates/hir_ty/src/diagnostics_sink.rs | 109 ++++++++++ crates/hir_ty/src/infer.rs | 5 +- crates/hir_ty/src/lib.rs | 1 + crates/ide/src/diagnostics.rs | 65 ++++-- 25 files changed, 801 insertions(+), 784 deletions(-) delete mode 100644 crates/hir_def/src/body/diagnostics.rs delete mode 100644 crates/hir_def/src/diagnostics.rs create mode 100644 crates/hir_def/src/nameres/diagnostics.rs delete mode 100644 crates/hir_expand/src/diagnostics.rs create mode 100644 crates/hir_ty/src/diagnostics_sink.rs (limited to 'crates') diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 414c3f35e..22ec7c6ac 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -3,13 +3,227 @@ //! //! This probably isn't the best way to do this -- ideally, diagnistics should //! be expressed in terms of hir types themselves. -pub use hir_def::diagnostics::{ - InactiveCode, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, -}; -pub use hir_expand::diagnostics::{ - Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, -}; -pub use hir_ty::diagnostics::{ - IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, - NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, +use std::any::Any; + +use cfg::{CfgExpr, CfgOptions, DnfExpr}; +use hir_def::path::ModPath; +use hir_expand::{HirFileId, InFile}; +use stdx::format_to; +use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; + +pub use hir_ty::{ + diagnostics::{ + IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, + MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon, + ReplaceFilterMapNextWithFindMap, + }, + diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder}, }; + +// Diagnostic: unresolved-module +// +// This diagnostic is triggered if rust-analyzer is unable to discover referred module. +#[derive(Debug)] +pub struct UnresolvedModule { + pub file: HirFileId, + pub decl: AstPtr, + 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 { + 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, +} + +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 { + 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, +} + +impl Diagnostic for UnresolvedImport { + fn code(&self) -> DiagnosticCode { + DiagnosticCode("unresolved-import") + } + fn message(&self) -> String { + "unresolved import".to_string() + } + fn display_source(&self) -> InFile { + 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 = "../"]` 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, + 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 { + 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 { + 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, + pub macro_name: Option, +} + +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 { + 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 { + 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 cdf65a044..1ecd2391b 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -35,12 +35,18 @@ use std::{iter, sync::Arc}; use arrayvec::ArrayVec; use base_db::{CrateDisplayName, CrateId, Edition, FileId}; +use diagnostics::{ + InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, + UnresolvedModule, UnresolvedProcMacro, +}; use either::Either; use hir_def::{ adt::{ReprKind, VariantData}, + body::BodyDiagnostic, expr::{BindingAnnotation, LabelId, Pat, PatId}, item_tree::ItemTreeNode, lang_item::LangItemTarget, + nameres, per_ns::PerNs, resolver::{HasResolver, Resolver}, src::HasSource as _, @@ -50,11 +56,12 @@ use hir_def::{ LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, }; -use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind}; +use hir_expand::{name::name, MacroCallKind, MacroDefKind}; use hir_ty::{ autoderef, consteval::ConstExt, could_unify, + diagnostics_sink::DiagnosticSink, method_resolution::{self, def_crates, TyFingerprint}, primitive::UintTy, subst_prefix, @@ -65,11 +72,12 @@ use hir_ty::{ WhereClause, }; use itertools::Itertools; +use nameres::diagnostics::DefDiagnosticKind; use rustc_hash::FxHashSet; use stdx::{format_to, impl_from}; use syntax::{ ast::{self, AttrsOwner, NameOwner}, - AstNode, SmolStr, + AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr, }; use tt::{Ident, Leaf, Literal, TokenTree}; @@ -442,7 +450,137 @@ impl Module { format!("{:?}", self.name(db).map_or("".into(), |name| name.to_string())) }); let def_map = self.id.def_map(db.upcast()); - def_map.add_diagnostics(db.upcast(), self.id.local_id, sink); + for diag in def_map.diagnostics() { + if diag.in_module != self.id.local_id { + // FIXME: This is accidentally quadratic. + continue; + } + match &diag.kind { + DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => { + let decl = declaration.to_node(db.upcast()); + sink.push(UnresolvedModule { + file: declaration.file_id, + decl: AstPtr::new(&decl), + candidate: candidate.clone(), + }) + } + DefDiagnosticKind::UnresolvedExternCrate { ast } => { + let item = ast.to_node(db.upcast()); + sink.push(UnresolvedExternCrate { + file: ast.file_id, + item: AstPtr::new(&item), + }); + } + + DefDiagnosticKind::UnresolvedImport { ast, index } => { + let use_item = ast.to_node(db.upcast()); + let hygiene = Hygiene::new(db.upcast(), ast.file_id); + let mut cur = 0; + let mut tree = None; + ModPath::expand_use_item( + db.upcast(), + InFile::new(ast.file_id, use_item), + &hygiene, + |_mod_path, use_tree, _is_glob, _alias| { + if cur == *index { + tree = Some(use_tree.clone()); + } + + cur += 1; + }, + ); + + if let Some(tree) = tree { + sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) }); + } + } + + DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { + let item = ast.to_node(db.upcast()); + sink.push(InactiveCode { + file: ast.file_id, + node: AstPtr::new(&item).into(), + cfg: cfg.clone(), + opts: opts.clone(), + }); + } + + DefDiagnosticKind::UnresolvedProcMacro { ast } => { + let mut precise_location = None; + let (file, ast, name) = match ast { + MacroCallKind::FnLike { ast_id, .. } => { + let node = ast_id.to_node(db.upcast()); + (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None) + } + MacroCallKind::Derive { ast_id, derive_name, .. } => { + let node = ast_id.to_node(db.upcast()); + + // Compute the precise location of the macro name's token in the derive + // list. + // FIXME: This does not handle paths to the macro, but neither does the + // rest of r-a. + let derive_attrs = + node.attrs().filter_map(|attr| match attr.as_simple_call() { + Some((name, args)) if name == "derive" => Some(args), + _ => None, + }); + 'outer: for attr in derive_attrs { + let tokens = + attr.syntax().children_with_tokens().filter_map(|elem| { + match elem { + syntax::NodeOrToken::Node(_) => None, + syntax::NodeOrToken::Token(tok) => Some(tok), + } + }); + for token in tokens { + if token.kind() == SyntaxKind::IDENT + && token.text() == derive_name.as_str() + { + precise_location = Some(token.text_range()); + break 'outer; + } + } + } + + ( + ast_id.file_id, + SyntaxNodePtr::from(AstPtr::new(&node)), + Some(derive_name.clone()), + ) + } + }; + sink.push(UnresolvedProcMacro { + file, + node: ast, + precise_location, + macro_name: name, + }); + } + + DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { + let node = ast.to_node(db.upcast()); + sink.push(UnresolvedMacroCall { + file: ast.file_id, + node: AstPtr::new(&node), + path: path.clone(), + }); + } + + DefDiagnosticKind::MacroError { ast, message } => { + let (file, ast) = match ast { + MacroCallKind::FnLike { ast_id, .. } => { + let node = ast_id.to_node(db.upcast()); + (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) + } + MacroCallKind::Derive { ast_id, .. } => { + let node = ast_id.to_node(db.upcast()); + (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) + } + }; + sink.push(MacroError { file, node: ast, message: message.clone() }); + } + } + } for decl in self.declarations(db) { match decl { crate::ModuleDef::Function(f) => f.diagnostics(db, sink), @@ -865,7 +1003,37 @@ impl Function { pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { let krate = self.module(db).id.krate(); - hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink); + + let source_map = db.body_with_source_map(self.id.into()).1; + for diag in source_map.diagnostics() { + match diag { + BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode { + file: node.file_id, + node: node.value.clone(), + cfg: cfg.clone(), + opts: opts.clone(), + }), + BodyDiagnostic::MacroError { node, message } => sink.push(MacroError { + file: node.file_id, + node: node.value.clone().into(), + message: message.to_string(), + }), + BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro { + file: node.file_id, + node: node.value.clone().into(), + precise_location: None, + macro_name: None, + }), + BodyDiagnostic::UnresolvedMacroCall { node, path } => { + sink.push(UnresolvedMacroCall { + file: node.file_id, + node: node.value.clone(), + path: path.clone(), + }) + } + } + } + hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink); hir_ty::diagnostics::validate_body(db, self.id.into(), sink); } diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs index 98b485b60..c521879c8 100644 --- a/crates/hir_def/src/body.rs +++ b/crates/hir_def/src/body.rs @@ -1,7 +1,6 @@ //! Defines `Body`: a lowered representation of bodies of functions, statics and //! consts. mod lower; -mod diagnostics; #[cfg(test)] mod tests; pub mod scope; @@ -9,17 +8,16 @@ pub mod scope; use std::{mem, ops::Index, sync::Arc}; use base_db::CrateId; -use cfg::CfgOptions; +use cfg::{CfgExpr, CfgOptions}; use drop_bomb::DropBomb; use either::Either; use hir_expand::{ - ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult, - HirFileId, InFile, MacroDefId, + ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId, }; use la_arena::{Arena, ArenaMap}; use profile::Count; use rustc_hash::FxHashMap; -use syntax::{ast, AstNode, AstPtr}; +use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; use crate::{ attr::{Attrs, RawAttrs}, @@ -273,12 +271,20 @@ pub struct BodySourceMap { /// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in /// the source map (since they're just as volatile). - diagnostics: Vec, + diagnostics: Vec, } #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] pub struct SyntheticSyntax; +#[derive(Debug, Eq, PartialEq)] +pub enum BodyDiagnostic { + InactiveCode { node: InFile, cfg: CfgExpr, opts: CfgOptions }, + MacroError { node: InFile>, message: String }, + UnresolvedProcMacro { node: InFile> }, + UnresolvedMacroCall { node: InFile>, path: ModPath }, +} + impl Body { pub(crate) fn body_with_source_map_query( db: &dyn DefDatabase, @@ -416,9 +422,8 @@ impl BodySourceMap { self.field_map.get(&src).cloned() } - pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) { - for diag in &self.diagnostics { - diag.add_to(sink); - } + /// Get a reference to the body source map's diagnostics. + pub fn diagnostics(&self) -> &[BodyDiagnostic] { + &self.diagnostics } } diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs deleted file mode 100644 index f6992c9a8..000000000 --- a/crates/hir_def/src/body/diagnostics.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Diagnostics emitted during body lowering. - -use hir_expand::diagnostics::DiagnosticSink; - -use crate::diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro}; - -#[derive(Debug, Eq, PartialEq)] -pub(crate) enum BodyDiagnostic { - InactiveCode(InactiveCode), - MacroError(MacroError), - UnresolvedProcMacro(UnresolvedProcMacro), - UnresolvedMacroCall(UnresolvedMacroCall), -} - -impl BodyDiagnostic { - pub(crate) fn add_to(&self, sink: &mut DiagnosticSink<'_>) { - match self { - BodyDiagnostic::InactiveCode(diag) => { - sink.push(diag.clone()); - } - BodyDiagnostic::MacroError(diag) => { - sink.push(diag.clone()); - } - BodyDiagnostic::UnresolvedProcMacro(diag) => { - sink.push(diag.clone()); - } - BodyDiagnostic::UnresolvedMacroCall(diag) => { - sink.push(diag.clone()); - } - } - } -} diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs index 2a7e0205f..da1fdac33 100644 --- a/crates/hir_def/src/body/lower.rs +++ b/crates/hir_def/src/body/lower.rs @@ -8,7 +8,7 @@ use hir_expand::{ ast_id_map::{AstIdMap, FileAstId}, hygiene::Hygiene, name::{name, AsName, Name}, - ExpandError, HirFileId, + ExpandError, HirFileId, InFile, }; use la_arena::Arena; use profile::Count; @@ -23,9 +23,9 @@ use syntax::{ use crate::{ adt::StructKind, body::{Body, BodySourceMap, Expander, LabelSource, PatPtr, SyntheticSyntax}, + body::{BodyDiagnostic, ExprSource, PatSource}, builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, db::DefDatabase, - diagnostics::{InactiveCode, MacroError, UnresolvedMacroCall, UnresolvedProcMacro}, expr::{ dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Label, LabelId, Literal, LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, @@ -38,8 +38,6 @@ use crate::{ AdtId, BlockLoc, ModuleDefId, UnresolvedMacro, }; -use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; - pub struct LowerCtx<'a> { pub db: &'a dyn DefDatabase, hygiene: Hygiene, @@ -592,13 +590,10 @@ impl ExprCollector<'_> { let res = match res { Ok(res) => res, Err(UnresolvedMacro { path }) => { - self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall( - UnresolvedMacroCall { - file: outer_file, - node: syntax_ptr.cast().unwrap(), - path, - }, - )); + self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedMacroCall { + node: InFile::new(outer_file, syntax_ptr), + path, + }); collector(self, None); return; } @@ -606,21 +601,15 @@ impl ExprCollector<'_> { match &res.err { Some(ExpandError::UnresolvedProcMacro) => { - self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro( - UnresolvedProcMacro { - file: outer_file, - node: syntax_ptr.into(), - precise_location: None, - macro_name: None, - }, - )); + self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro { + node: InFile::new(outer_file, syntax_ptr), + }); } Some(err) => { - self.source_map.diagnostics.push(BodyDiagnostic::MacroError(MacroError { - file: outer_file, - node: syntax_ptr.into(), + self.source_map.diagnostics.push(BodyDiagnostic::MacroError { + node: InFile::new(outer_file, syntax_ptr), message: err.to_string(), - })); + }); } None => {} } @@ -945,12 +934,14 @@ impl ExprCollector<'_> { return Some(()); } - self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode { - file: self.expander.current_file_id, - node: SyntaxNodePtr::new(owner.syntax()), + self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode { + node: InFile::new( + self.expander.current_file_id, + SyntaxNodePtr::new(owner.syntax()), + ), cfg, opts: self.expander.cfg_options().clone(), - })); + }); None } diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs index 3e8f16306..d4fae05a6 100644 --- a/crates/hir_def/src/body/tests.rs +++ b/crates/hir_def/src/body/tests.rs @@ -96,26 +96,26 @@ fn f() { // The three g̶e̶n̶d̶e̶r̶s̶ statements: #[cfg(a)] fn f() {} // Item statement - //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^^^^^^^^^ InactiveCode #[cfg(a)] {} // Expression statement - //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^^ InactiveCode #[cfg(a)] let x = 0; // let statement - //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^^^^^^^^^^ InactiveCode abc(#[cfg(a)] 0); - //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^ InactiveCode let x = Struct { #[cfg(a)] f: 0, - //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^^^^ InactiveCode }; match () { () => (), #[cfg(a)] () => (), - //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^^^^^^^^ InactiveCode } #[cfg(a)] 0 // Trailing expression of block - //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled + //^^^^^^^^^^^ InactiveCode } ", ); @@ -188,7 +188,7 @@ fn unresolved_macro_diag() { r#" fn f() { m!(); - //^^^^ unresolved macro `m!` + //^^^^ UnresolvedMacroCall } "#, ); diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs deleted file mode 100644 index a71ae2668..000000000 --- a/crates/hir_def/src/diagnostics.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! Diagnostics produced by `hir_def`. - -use std::any::Any; -use stdx::format_to; - -use cfg::{CfgExpr, CfgOptions, DnfExpr}; -use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; -use hir_expand::{HirFileId, InFile}; -use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; - -use crate::{db::DefDatabase, path::ModPath, DefWithBodyId}; - -pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { - let source_map = db.body_with_source_map(owner).1; - source_map.add_diagnostics(db, sink); -} - -// Diagnostic: unresolved-module -// -// This diagnostic is triggered if rust-analyzer is unable to discover referred module. -#[derive(Debug)] -pub struct UnresolvedModule { - pub file: HirFileId, - pub decl: AstPtr, - 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 { - 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, -} - -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 { - 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, -} - -impl Diagnostic for UnresolvedImport { - fn code(&self) -> DiagnosticCode { - DiagnosticCode("unresolved-import") - } - fn message(&self) -> String { - "unresolved import".to_string() - } - fn display_source(&self) -> InFile { - 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 = "../"]` 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, - 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 { - 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 { - 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, - pub macro_name: Option, -} - -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 { - 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 { - 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 70001cac8..9aa95720a 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs @@ -19,7 +19,6 @@ pub mod path; pub mod type_ref; pub mod builtin_type; pub mod builtin_attr; -pub mod diagnostics; pub mod per_ns; pub mod item_scope; @@ -56,7 +55,6 @@ use std::{ sync::Arc, }; -use adt::VariantData; use base_db::{impl_intern_key, salsa, CrateId}; use hir_expand::{ ast_id_map::FileAstId, @@ -67,15 +65,18 @@ use hir_expand::{ use la_arena::Idx; use nameres::DefMap; use path::ModPath; +use stdx::impl_from; use syntax::ast; -use crate::attr::AttrId; -use crate::builtin_type::BuiltinType; -use item_tree::{ - Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, - TypeAlias, Union, +use crate::{ + adt::VariantData, + attr::AttrId, + builtin_type::BuiltinType, + item_tree::{ + Const, Enum, Function, Impl, ItemTreeId, ItemTreeNode, ModItem, Static, Struct, Trait, + TypeAlias, Union, + }, }; -use stdx::impl_from; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ModuleId { diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 249af6fc8..ebfcc26c4 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs @@ -47,18 +47,19 @@ //! path and, upon success, we run macro expansion and "collect module" phase on //! the result +pub mod diagnostics; mod collector; mod mod_resolution; mod path_resolution; +mod proc_macro; #[cfg(test)] mod tests; -mod proc_macro; use std::sync::Arc; use base_db::{CrateId, Edition, FileId}; -use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId}; +use hir_expand::{name::Name, InFile, MacroDefId}; use la_arena::Arena; use profile::Count; use rustc_hash::FxHashMap; @@ -254,15 +255,6 @@ impl DefMap { } } - pub fn add_diagnostics( - &self, - db: &dyn DefDatabase, - module: LocalModuleId, - sink: &mut DiagnosticSink, - ) { - self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink)) - } - pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator + '_ { self.modules .iter() @@ -448,6 +440,11 @@ impl DefMap { module.scope.shrink_to_fit(); } } + + /// Get a reference to the def map's diagnostics. + pub fn diagnostics(&self) -> &[DefDiagnostic] { + self.diagnostics.as_slice() + } } impl ModuleData { @@ -471,236 +468,3 @@ pub enum ModuleSource { Module(ast::Module), BlockExpr(ast::BlockExpr), } - -mod diagnostics { - use cfg::{CfgExpr, CfgOptions}; - use hir_expand::diagnostics::DiagnosticSink; - use hir_expand::hygiene::Hygiene; - use hir_expand::{InFile, MacroCallKind}; - use syntax::ast::AttrsOwner; - use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr}; - - use crate::path::ModPath; - use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; - - #[derive(Debug, PartialEq, Eq)] - enum DiagnosticKind { - UnresolvedModule { declaration: AstId, candidate: String }, - - UnresolvedExternCrate { ast: AstId }, - - UnresolvedImport { ast: AstId, index: usize }, - - UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, - - UnresolvedProcMacro { ast: MacroCallKind }, - - UnresolvedMacroCall { ast: AstId, 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, - candidate: String, - ) -> Self { - Self { - in_module: container, - kind: DiagnosticKind::UnresolvedModule { declaration, candidate }, - } - } - - pub(super) fn unresolved_extern_crate( - container: LocalModuleId, - declaration: AstId, - ) -> Self { - Self { - in_module: container, - kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration }, - } - } - - pub(super) fn unresolved_import( - container: LocalModuleId, - ast: AstId, - index: usize, - ) -> Self { - Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } } - } - - pub(super) fn unconfigured_code( - container: LocalModuleId, - ast: AstId, - 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, - 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 2ae740d0e..3ea472908 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -33,7 +33,10 @@ use crate::{ }, macro_call_as_call_id, nameres::{ - diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, + diagnostics::DefDiagnostic, + mod_resolution::ModDir, + path_resolution::ReachedFixedPoint, + proc_macro::{ProcMacroDef, ProcMacroKind}, BuiltinShadowMode, DefMap, ModuleData, ModuleOrigin, ResolveMode, }, path::{ImportAlias, ModPath, PathKind}, @@ -44,8 +47,6 @@ use crate::{ UnresolvedMacro, }; -use super::proc_macro::{ProcMacroDef, ProcMacroKind}; - const GLOB_RECURSION_LIMIT: usize = 100; const EXPANSION_DEPTH_LIMIT: usize = 128; const FIXED_POINT_LIMIT: usize = 8192; diff --git a/crates/hir_def/src/nameres/diagnostics.rs b/crates/hir_def/src/nameres/diagnostics.rs new file mode 100644 index 000000000..8f2f0ff9f --- /dev/null +++ b/crates/hir_def/src/nameres/diagnostics.rs @@ -0,0 +1,90 @@ +//! Diagnostics emitted during DefMap construction. + +use cfg::{CfgExpr, CfgOptions}; +use hir_expand::MacroCallKind; +use syntax::ast; + +use crate::{nameres::LocalModuleId, path::ModPath, AstId}; + +#[derive(Debug, PartialEq, Eq)] +pub enum DefDiagnosticKind { + UnresolvedModule { ast: AstId, candidate: String }, + + UnresolvedExternCrate { ast: AstId }, + + UnresolvedImport { ast: AstId, index: usize }, + + UnconfiguredCode { ast: AstId, cfg: CfgExpr, opts: CfgOptions }, + + UnresolvedProcMacro { ast: MacroCallKind }, + + UnresolvedMacroCall { ast: AstId, 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, + candidate: String, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate }, + } + } + + pub(super) fn unresolved_extern_crate( + container: LocalModuleId, + declaration: AstId, + ) -> Self { + Self { + in_module: container, + kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration }, + } + } + + pub(super) fn unresolved_import( + container: LocalModuleId, + ast: AstId, + index: usize, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } } + } + + pub(super) fn unconfigured_code( + container: LocalModuleId, + ast: AstId, + 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, + path: ModPath, + ) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } } + } +} diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs index 75147d973..ec6670952 100644 --- a/crates/hir_def/src/nameres/tests/diagnostics.rs +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs @@ -18,40 +18,13 @@ fn unresolved_import() { r" use does_exist; use does_not_exist; - //^^^^^^^^^^^^^^ unresolved import + //^^^^^^^^^^^^^^^^^^^ UnresolvedImport mod does_exist {} ", ); } -#[test] -fn unresolved_import_in_use_tree() { - // Only the relevant part of a nested `use` item should be highlighted. - check_diagnostics( - r" - use does_exist::{Exists, DoesntExist}; - //^^^^^^^^^^^ unresolved import - - use {does_not_exist::*, does_exist}; - //^^^^^^^^^^^^^^^^^ unresolved import - - use does_not_exist::{ - a, - //^ unresolved import - b, - //^ unresolved import - c, - //^ unresolved import - }; - - mod does_exist { - pub struct Exists; - } - ", - ); -} - #[test] fn unresolved_extern_crate() { check_diagnostics( @@ -59,7 +32,7 @@ fn unresolved_extern_crate() { //- /main.rs crate:main deps:core extern crate core; extern crate doesnotexist; - //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate + //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate //- /lib.rs crate:core ", ); @@ -72,7 +45,7 @@ fn extern_crate_self_as() { r" //- /lib.rs extern crate doesnotexist; - //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate + //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate // Should not error. extern crate self as foo; struct Foo; @@ -88,18 +61,18 @@ fn dedup_unresolved_import_from_unresolved_crate() { //- /main.rs crate:main mod a { extern crate doesnotexist; - //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate + //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate // Should not error, since we already errored for the missing crate. use doesnotexist::{self, bla, *}; use crate::doesnotexist; - //^^^^^^^^^^^^^^^^^^^ unresolved import + //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport } mod m { use super::doesnotexist; - //^^^^^^^^^^^^^^^^^^^ unresolved import + //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport } ", ); @@ -112,7 +85,7 @@ fn unresolved_module() { //- /lib.rs mod foo; mod bar; - //^^^^^^^^ unresolved module + //^^^^^^^^ UnresolvedModule mod baz {} //- /foo.rs ", @@ -127,16 +100,16 @@ fn inactive_item() { r#" //- /lib.rs #[cfg(no)] pub fn f() {} - //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled + //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode #[cfg(no)] #[cfg(no2)] mod m; - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode #[cfg(all(not(a), b))] enum E {} - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode #[cfg(feature = "std")] use std; - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode "#, ); } @@ -149,14 +122,14 @@ fn inactive_via_cfg_attr() { r#" //- /lib.rs #[cfg_attr(not(never), cfg(no))] fn f() {} - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode #[cfg_attr(not(never), cfg(not(no)))] fn f() {} #[cfg_attr(never, cfg(no))] fn g() {} #[cfg_attr(not(never), inline, cfg(no))] fn h() {} - //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled + //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode "#, ); } @@ -170,7 +143,7 @@ fn unresolved_legacy_scope_macro() { m!(); m2!(); - //^^^^^^ unresolved macro `self::m2!` + //^^^^^^ UnresolvedMacroCall "#, ); } @@ -187,7 +160,7 @@ fn unresolved_module_scope_macro() { self::m!(); self::m2!(); - //^^^^^^^^^^^^ unresolved macro `self::m2!` + //^^^^^^^^^^^^ UnresolvedMacroCall "#, ); } diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs index 45ab9d0ff..d9ec03d2d 100644 --- a/crates/hir_def/src/path.rs +++ b/crates/hir_def/src/path.rs @@ -71,7 +71,7 @@ impl ModPath { } /// Calls `cb` with all paths, represented by this use item. - pub(crate) fn expand_use_item( + pub fn expand_use_item( db: &dyn DefDatabase, item_src: InFile, hygiene: &Hygiene, diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs index 8fa703a57..6c357c915 100644 --- a/crates/hir_def/src/test_db.rs +++ b/crates/hir_def/src/test_db.rs @@ -5,19 +5,20 @@ use std::{ sync::{Arc, Mutex}, }; -use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast}; +use base_db::{ + salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast, +}; use base_db::{AnchoredPath, SourceDatabase}; -use hir_expand::diagnostics::Diagnostic; -use hir_expand::diagnostics::DiagnosticSinkBuilder; use hir_expand::{db::AstDatabase, InFile}; use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; -use syntax::{algo, ast, AstNode, TextRange, TextSize}; +use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize}; use test_utils::extract_annotations; use crate::{ + body::BodyDiagnostic, db::DefDatabase, - nameres::{DefMap, ModuleSource}, + nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource}, src::HasSource, LocalModuleId, Lookup, ModuleDefId, ModuleId, }; @@ -262,19 +263,70 @@ impl TestDB { .collect() } - pub(crate) fn diagnostics(&self, mut cb: F) { + pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) { let crate_graph = self.crate_graph(); for krate in crate_graph.iter() { let crate_def_map = self.crate_def_map(krate); - let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); - for (module_id, module) in crate_def_map.modules() { - crate_def_map.add_diagnostics(self, module_id, &mut sink); + for diag in crate_def_map.diagnostics() { + let (node, message): (InFile, &str) = match &diag.kind { + DefDiagnosticKind::UnresolvedModule { ast, .. } => { + let node = ast.to_node(self.upcast()); + (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule") + } + DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => { + let node = ast.to_node(self.upcast()); + (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate") + } + DefDiagnosticKind::UnresolvedImport { ast, .. } => { + let node = ast.to_node(self.upcast()); + (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport") + } + DefDiagnosticKind::UnconfiguredCode { ast, .. } => { + let node = ast.to_node(self.upcast()); + (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode") + } + DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => { + (ast.to_node(self.upcast()), "UnresolvedProcMacro") + } + DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => { + let node = ast.to_node(self.upcast()); + (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall") + } + DefDiagnosticKind::MacroError { ast, message } => { + (ast.to_node(self.upcast()), message.as_str()) + } + }; + + let frange = node.as_ref().original_file_range(self); + cb(frange, message.to_string()) + } + for (_module_id, module) in crate_def_map.modules() { for decl in module.scope.declarations() { if let ModuleDefId::FunctionId(it) = decl { let source_map = self.body_with_source_map(it.into()).1; - source_map.add_diagnostics(self, &mut sink); + for diag in source_map.diagnostics() { + let (ptr, message): (InFile, &str) = match diag { + BodyDiagnostic::InactiveCode { node, .. } => { + (node.clone().map(|it| it.into()), "InactiveCode") + } + BodyDiagnostic::MacroError { node, message } => { + (node.clone().map(|it| it.into()), message.as_str()) + } + BodyDiagnostic::UnresolvedProcMacro { node } => { + (node.clone().map(|it| it.into()), "UnresolvedProcMacro") + } + BodyDiagnostic::UnresolvedMacroCall { node, .. } => { + (node.clone().map(|it| it.into()), "UnresolvedMacroCall") + } + }; + + let root = self.parse_or_expand(ptr.file_id).unwrap(); + let node = ptr.map(|ptr| ptr.to_node(&root)); + let frange = node.as_ref().original_file_range(self); + cb(frange, message.to_string()) + } } } } @@ -287,14 +339,7 @@ impl TestDB { assert!(!annotations.is_empty()); let mut actual: FxHashMap> = FxHashMap::default(); - db.diagnostics(|d| { - let src = d.display_source(); - let root = db.parse_or_expand(src.file_id).unwrap(); - - let node = src.map(|ptr| ptr.to_node(&root)); - let frange = node.as_ref().original_file_range(db); - - let message = d.message(); + db.diagnostics(&mut |frange, message| { actual.entry(frange.file_id).or_default().push((frange.range, message)); }); @@ -319,7 +364,7 @@ impl TestDB { assert!(annotations.is_empty()); let mut has_diagnostics = false; - db.diagnostics(|_| { + db.diagnostics(&mut |_, _| { has_diagnostics = true; }); diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 625c26f0a..e8f4af309 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -186,7 +186,7 @@ fn parse_macro_expansion( // The final goal we would like to make all parse_macro success, // such that the following log will not call anyway. let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); - let node = loc.kind.node(db); + let node = loc.kind.to_node(db); // collect parent information for warning log let parents = diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_expand/src/diagnostics.rs deleted file mode 100644 index bf0b85ce9..000000000 --- a/crates/hir_expand/src/diagnostics.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Semantic errors and warnings. -//! -//! The `Diagnostic` trait defines a trait object which can represent any -//! diagnostic. -//! -//! `DiagnosticSink` struct is used as an emitter for diagnostic. When creating -//! a `DiagnosticSink`, you supply a callback which can react to a `dyn -//! Diagnostic` or to any concrete diagnostic (downcasting is used internally). -//! -//! Because diagnostics store file offsets, it's a bad idea to store them -//! directly in salsa. For this reason, every hir subsytem defines it's own -//! strongly-typed closed set of diagnostics which use hir ids internally, are -//! stored in salsa and do *not* implement the `Diagnostic` trait. Instead, a -//! subsystem provides a separate, non-query-based API which can walk all stored -//! values and transform them into instances of `Diagnostic`. - -use std::{any::Any, fmt}; - -use syntax::SyntaxNodePtr; - -use crate::InFile; - -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct DiagnosticCode(pub &'static str); - -impl DiagnosticCode { - pub fn as_str(&self) -> &str { - self.0 - } -} - -pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { - fn code(&self) -> DiagnosticCode; - fn message(&self) -> String; - /// Source element that triggered the diagnostics. - /// - /// Note that this should reflect "semantics", rather than specific span we - /// want to highlight. When rendering the diagnostics into an error message, - /// the IDE will fetch the `SyntaxNode` and will narrow the span - /// appropriately. - fn display_source(&self) -> InFile; - fn as_any(&self) -> &(dyn Any + Send + 'static); - fn is_experimental(&self) -> bool { - false - } -} - -pub struct DiagnosticSink<'a> { - callbacks: Vec Result<(), ()> + 'a>>, - filters: Vec bool + 'a>>, - default_callback: Box, -} - -impl<'a> DiagnosticSink<'a> { - pub fn push(&mut self, d: impl Diagnostic) { - let d: &dyn Diagnostic = &d; - self._push(d); - } - - fn _push(&mut self, d: &dyn Diagnostic) { - for filter in &mut self.filters { - if !filter(d) { - return; - } - } - for cb in &mut self.callbacks { - match cb(d) { - Ok(()) => return, - Err(()) => (), - } - } - (self.default_callback)(d) - } -} - -pub struct DiagnosticSinkBuilder<'a> { - callbacks: Vec Result<(), ()> + 'a>>, - filters: Vec bool + 'a>>, -} - -impl<'a> DiagnosticSinkBuilder<'a> { - pub fn new() -> Self { - Self { callbacks: Vec::new(), filters: Vec::new() } - } - - pub fn filter bool + 'a>(mut self, cb: F) -> Self { - self.filters.push(Box::new(cb)); - self - } - - pub fn on(mut self, mut cb: F) -> Self { - let cb = move |diag: &dyn Diagnostic| match diag.as_any().downcast_ref::() { - Some(d) => { - cb(d); - Ok(()) - } - None => Err(()), - }; - self.callbacks.push(Box::new(cb)); - self - } - - pub fn build(self, default_callback: F) -> DiagnosticSink<'a> { - DiagnosticSink { - callbacks: self.callbacks, - filters: self.filters, - default_callback: Box::new(default_callback), - } - } -} diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 6be4516a3..10d37234e 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -8,7 +8,6 @@ pub mod db; pub mod ast_id_map; pub mod name; pub mod hygiene; -pub mod diagnostics; pub mod builtin_derive; pub mod builtin_macro; pub mod proc_macro; @@ -108,7 +107,7 @@ impl HirFileId { HirFileIdRepr::FileId(_) => None, HirFileIdRepr::MacroFile(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); - Some(loc.kind.node(db)) + Some(loc.kind.to_node(db)) } } } @@ -153,7 +152,7 @@ impl HirFileId { HirFileIdRepr::MacroFile(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); let item = match loc.def.kind { - MacroDefKind::BuiltInDerive(..) => loc.kind.node(db), + MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db), _ => return None, }; Some(item.with_value(ast::Item::cast(item.value.clone())?)) @@ -269,7 +268,7 @@ impl MacroCallKind { } } - fn node(&self, db: &dyn db::AstDatabase) -> InFile { + pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile { 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 84fc8ce14..7598e2193 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs @@ -8,12 +8,14 @@ use std::{any::Any, fmt}; use base_db::CrateId; use hir_def::{DefWithBodyId, ModuleDefId}; -use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; use hir_expand::{name::Name, HirFileId, InFile}; use stdx::format_to; use syntax::{ast, AstPtr, SyntaxNodePtr}; -use crate::db::HirDatabase; +use crate::{ + db::HirDatabase, + diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink}, +}; pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; @@ -446,15 +448,13 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap { mod tests { use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId}; - use hir_expand::{ - db::AstDatabase, - diagnostics::{Diagnostic, DiagnosticSinkBuilder}, - }; + use hir_expand::db::AstDatabase; use rustc_hash::FxHashMap; use syntax::{TextRange, TextSize}; use crate::{ diagnostics::{validate_body, validate_module_item}, + diagnostics_sink::{Diagnostic, DiagnosticSinkBuilder}, test_db::TestDB, }; diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs index 075dc4131..ef982cbcd 100644 --- a/crates/hir_ty/src/diagnostics/decl_check.rs +++ b/crates/hir_ty/src/diagnostics/decl_check.rs @@ -19,10 +19,7 @@ use hir_def::{ src::HasSource, AdtId, AttrDefId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, }; -use hir_expand::{ - diagnostics::DiagnosticSink, - name::{AsName, Name}, -}; +use hir_expand::name::{AsName, Name}; use stdx::{always, never}; use syntax::{ ast::{self, NameOwner}, @@ -32,6 +29,7 @@ use syntax::{ use crate::{ db::HirDatabase, diagnostics::{decl_check::case_conv::*, CaseType, IdentType, IncorrectCase}, + diagnostics_sink::DiagnosticSink, }; mod allow { diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs index d1f113e7f..86f82e3fa 100644 --- a/crates/hir_ty/src/diagnostics/expr.rs +++ b/crates/hir_ty/src/diagnostics/expr.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use hir_def::{expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId}; -use hir_expand::{diagnostics::DiagnosticSink, name}; +use hir_expand::name; use rustc_hash::FxHashSet; use syntax::{ast, AstPtr}; @@ -16,6 +16,7 @@ use crate::{ MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, MissingPatFields, RemoveThisSemicolon, }, + diagnostics_sink::DiagnosticSink, AdtId, InferenceResult, Interner, TyExt, TyKind, }; diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs index 5d13bddea..c3c483425 100644 --- a/crates/hir_ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs @@ -9,10 +9,10 @@ use hir_def::{ resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, }; -use hir_expand::diagnostics::DiagnosticSink; use crate::{ - db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Interner, TyExt, TyKind, + db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult, + Interner, TyExt, TyKind, }; pub(super) struct UnsafeValidator<'a, 'b: 'a> { diff --git a/crates/hir_ty/src/diagnostics_sink.rs b/crates/hir_ty/src/diagnostics_sink.rs new file mode 100644 index 000000000..084fa8b06 --- /dev/null +++ b/crates/hir_ty/src/diagnostics_sink.rs @@ -0,0 +1,109 @@ +//! Semantic errors and warnings. +//! +//! The `Diagnostic` trait defines a trait object which can represent any +//! diagnostic. +//! +//! `DiagnosticSink` struct is used as an emitter for diagnostic. When creating +//! a `DiagnosticSink`, you supply a callback which can react to a `dyn +//! Diagnostic` or to any concrete diagnostic (downcasting is used internally). +//! +//! Because diagnostics store file offsets, it's a bad idea to store them +//! directly in salsa. For this reason, every hir subsytem defines it's own +//! strongly-typed closed set of diagnostics which use hir ids internally, are +//! stored in salsa and do *not* implement the `Diagnostic` trait. Instead, a +//! subsystem provides a separate, non-query-based API which can walk all stored +//! values and transform them into instances of `Diagnostic`. + +use std::{any::Any, fmt}; + +use hir_expand::InFile; +use syntax::SyntaxNodePtr; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct DiagnosticCode(pub &'static str); + +impl DiagnosticCode { + pub fn as_str(&self) -> &str { + self.0 + } +} + +pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { + fn code(&self) -> DiagnosticCode; + fn message(&self) -> String; + /// Source element that triggered the diagnostics. + /// + /// Note that this should reflect "semantics", rather than specific span we + /// want to highlight. When rendering the diagnostics into an error message, + /// the IDE will fetch the `SyntaxNode` and will narrow the span + /// appropriately. + fn display_source(&self) -> InFile; + fn as_any(&self) -> &(dyn Any + Send + 'static); + fn is_experimental(&self) -> bool { + false + } +} + +pub struct DiagnosticSink<'a> { + callbacks: Vec Result<(), ()> + 'a>>, + filters: Vec bool + 'a>>, + default_callback: Box, +} + +impl<'a> DiagnosticSink<'a> { + pub fn push(&mut self, d: impl Diagnostic) { + let d: &dyn Diagnostic = &d; + self._push(d); + } + + fn _push(&mut self, d: &dyn Diagnostic) { + for filter in &mut self.filters { + if !filter(d) { + return; + } + } + for cb in &mut self.callbacks { + match cb(d) { + Ok(()) => return, + Err(()) => (), + } + } + (self.default_callback)(d) + } +} + +pub struct DiagnosticSinkBuilder<'a> { + callbacks: Vec Result<(), ()> + 'a>>, + filters: Vec bool + 'a>>, +} + +impl<'a> DiagnosticSinkBuilder<'a> { + pub fn new() -> Self { + Self { callbacks: Vec::new(), filters: Vec::new() } + } + + pub fn filter bool + 'a>(mut self, cb: F) -> Self { + self.filters.push(Box::new(cb)); + self + } + + pub fn on(mut self, mut cb: F) -> Self { + let cb = move |diag: &dyn Diagnostic| match diag.as_any().downcast_ref::() { + Some(d) => { + cb(d); + Ok(()) + } + None => Err(()), + }; + self.callbacks.push(Box::new(cb)); + self + } + + pub fn build(self, default_callback: F) -> DiagnosticSink<'a> { + DiagnosticSink { + callbacks: self.callbacks, + filters: self.filters, + default_callback: Box::new(default_callback), + } + } +} diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index db3c937ff..174b7471e 100644 --- a/crates/hir_ty/src/infer.rs +++ b/crates/hir_ty/src/infer.rs @@ -28,13 +28,14 @@ use hir_def::{ AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule, Lookup, TraitId, TypeAliasId, VariantId, }; -use hir_expand::{diagnostics::DiagnosticSink, name::name}; +use hir_expand::name::name; use la_arena::ArenaMap; use rustc_hash::FxHashMap; use stdx::impl_from; use syntax::SmolStr; use super::{DomainGoal, InEnvironment, ProjectionTy, TraitEnvironment, TraitRef, Ty}; +use crate::diagnostics_sink::DiagnosticSink; use crate::{ db::HirDatabase, fold_tys, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, to_assoc_type_id, AliasEq, AliasTy, Goal, Interner, Substitution, @@ -793,11 +794,11 @@ impl std::ops::BitOrAssign for Diverges { mod diagnostics { use hir_def::{expr::ExprId, DefWithBodyId}; - use hir_expand::diagnostics::DiagnosticSink; use crate::{ db::HirDatabase, diagnostics::{BreakOutsideOfLoop, NoSuchField}, + diagnostics_sink::DiagnosticSink, }; #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/crates/hir_ty/src/lib.rs b/crates/hir_ty/src/lib.rs index ef021978a..50e0d6333 100644 --- a/crates/hir_ty/src/lib.rs +++ b/crates/hir_ty/src/lib.rs @@ -21,6 +21,7 @@ mod utils; mod walk; pub mod db; pub mod diagnostics; +pub mod diagnostics_sink; pub mod display; pub mod method_resolution; pub mod primitive; diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 27d347dbd..dcac7c76d 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -299,10 +299,10 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { #[cfg(test)] mod tests { - use expect_test::{expect, Expect}; + use expect_test::Expect; use ide_assists::AssistResolveStrategy; use stdx::trim_indent; - use test_utils::assert_eq_text; + use test_utils::{assert_eq_text, extract_annotations}; use crate::{fixture, DiagnosticsConfig}; @@ -396,26 +396,51 @@ mod tests { expect.assert_debug_eq(&diagnostics) } + pub(crate) fn check_diagnostics(ra_fixture: &str) { + let (analysis, file_id) = fixture::file(ra_fixture); + let diagnostics = analysis + .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) + .unwrap(); + + let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); + let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::>(); + assert_eq!(expected, actual); + } + #[test] fn test_unresolved_macro_range() { - check_expect( - r#"foo::bar!(92);"#, - expect![[r#" - [ - Diagnostic { - message: "unresolved macro `foo::bar!`", - range: 5..8, - severity: Error, - fixes: None, - unused: false, - code: Some( - DiagnosticCode( - "unresolved-macro-call", - ), - ), - }, - ] - "#]], + check_diagnostics( + r#" +foo::bar!(92); + //^^^ unresolved macro `foo::bar!` +"#, + ); + } + + #[test] + fn unresolved_import_in_use_tree() { + // Only the relevant part of a nested `use` item should be highlighted. + check_diagnostics( + r#" +use does_exist::{Exists, DoesntExist}; + //^^^^^^^^^^^ unresolved import + +use {does_not_exist::*, does_exist}; + //^^^^^^^^^^^^^^^^^ unresolved import + +use does_not_exist::{ + a, + //^ unresolved import + b, + //^ unresolved import + c, + //^ unresolved import +}; + +mod does_exist { + pub struct Exists; +} +"#, ); } -- cgit v1.2.3