diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index 414c3f35ee0..22ec7c6acaf 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 cdf65a04450..1ecd2391b0f 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 98b485b6020..c521879c873 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 f6992c9a8a9..00000000000 --- 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 2a7e0205f5b..da1fdac33ae 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 3e8f16306b3..d4fae05a699 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 a71ae266845..00000000000 --- 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 70001cac866..9aa95720af6 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 249af6fc806..ebfcc26c413 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 2ae740d0e65..3ea47290803 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 00000000000..8f2f0ff9f57 --- /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 75147d973a2..ec667095284 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 45ab9d0ffbf..d9ec03d2dae 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 8fa703a574e..6c357c915b5 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 625c26f0a09..e8f4af30951 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -186,7 +186,7 @@ fn parse_macro_expansion( // The final goal we would like to make all parse_macro success, // such that the following log will not call anyway. let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id); - let node = loc.kind.node(db); + let node = loc.kind.to_node(db); // collect parent information for warning log let parents = diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 6be4516a327..10d37234e77 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 84fc8ce14de..7598e2193aa 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 075dc4131fa..ef982cbcd4f 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 d1f113e7ff9..86f82e3fa45 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 5d13bddea35..c3c483425e7 100644 --- a/crates/hir_ty/src/diagnostics/unsafe_check.rs +++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs @@ -9,10 +9,10 @@ use hir_def::{ resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, }; -use hir_expand::diagnostics::DiagnosticSink; use crate::{ - db::HirDatabase, diagnostics::MissingUnsafe, InferenceResult, Interner, TyExt, TyKind, + db::HirDatabase, diagnostics::MissingUnsafe, diagnostics_sink::DiagnosticSink, InferenceResult, + Interner, TyExt, TyKind, }; pub(super) struct UnsafeValidator<'a, 'b: 'a> { diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_ty/src/diagnostics_sink.rs similarity index 99% rename from crates/hir_expand/src/diagnostics.rs rename to crates/hir_ty/src/diagnostics_sink.rs index bf0b85ce9d2..084fa8b06aa 100644 --- a/crates/hir_expand/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics_sink.rs @@ -16,10 +16,9 @@ use std::{any::Any, fmt}; +use hir_expand::InFile; use syntax::SyntaxNodePtr; -use crate::InFile; - #[derive(Copy, Clone, Debug, PartialEq)] pub struct DiagnosticCode(pub &'static str); diff --git a/crates/hir_ty/src/infer.rs b/crates/hir_ty/src/infer.rs index 164e8505044..8cefd80f3ab 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, @@ -798,11 +799,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 ef021978ade..50e0d633333 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 27d347dbd92..dcac7c76d20 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; +} +"#, ); }