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.
This commit is contained in:
Aleksey Kladov 2021-05-23 23:31:59 +03:00
parent b7414fa14a
commit 5c9f31d4c2
24 changed files with 693 additions and 676 deletions

View File

@ -3,13 +3,227 @@
//!
//! This probably isn't the best way to do this -- ideally, diagnistics should
//! be expressed in terms of hir types themselves.
pub use hir_def::diagnostics::{
InactiveCode, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
};
pub use hir_expand::diagnostics::{
Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
};
pub use hir_ty::diagnostics::{
IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
use std::any::Any;
use cfg::{CfgExpr, CfgOptions, DnfExpr};
use hir_def::path::ModPath;
use hir_expand::{HirFileId, InFile};
use stdx::format_to;
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
pub use hir_ty::{
diagnostics::{
IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms,
MissingOkOrSomeInTailExpr, NoSuchField, RemoveThisSemicolon,
ReplaceFilterMapNextWithFindMap,
},
diagnostics_sink::{Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder},
};
// Diagnostic: unresolved-module
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
#[derive(Debug)]
pub struct UnresolvedModule {
pub file: HirFileId,
pub decl: AstPtr<ast::Module>,
pub candidate: String,
}
impl Diagnostic for UnresolvedModule {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-module")
}
fn message(&self) -> String {
"unresolved module".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.decl.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: unresolved-extern-crate
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
#[derive(Debug)]
pub struct UnresolvedExternCrate {
pub file: HirFileId,
pub item: AstPtr<ast::ExternCrate>,
}
impl Diagnostic for UnresolvedExternCrate {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-extern-crate")
}
fn message(&self) -> String {
"unresolved extern crate".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.item.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
#[derive(Debug)]
pub struct UnresolvedImport {
pub file: HirFileId,
pub node: AstPtr<ast::UseTree>,
}
impl Diagnostic for UnresolvedImport {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-import")
}
fn message(&self) -> String {
"unresolved import".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
// This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
// - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
// - proc macros and/or proc macro generated code
true
}
}
// Diagnostic: unresolved-macro-call
//
// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
// macro in a macro invocation.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedMacroCall {
pub file: HirFileId,
pub node: AstPtr<ast::MacroCall>,
pub path: ModPath,
}
impl Diagnostic for UnresolvedMacroCall {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-macro-call")
}
fn message(&self) -> String {
format!("unresolved macro `{}!`", self.path)
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
true
}
}
// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InactiveCode {
pub file: HirFileId,
pub node: SyntaxNodePtr,
pub cfg: CfgExpr,
pub opts: CfgOptions,
}
impl Diagnostic for InactiveCode {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("inactive-code")
}
fn message(&self) -> String {
let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
let mut buf = "code is inactive due to #[cfg] directives".to_string();
if let Some(inactive) = inactive {
format_to!(buf, ": {}", inactive);
}
buf
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: unresolved-proc-macro
//
// This diagnostic is shown when a procedural macro can not be found. This usually means that
// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
// but can also indicate project setup problems.
//
// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedProcMacro {
pub file: HirFileId,
pub node: SyntaxNodePtr,
/// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
/// to use instead.
pub precise_location: Option<TextRange>,
pub macro_name: Option<String>,
}
impl Diagnostic for UnresolvedProcMacro {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-proc-macro")
}
fn message(&self) -> String {
match &self.macro_name {
Some(name) => format!("proc macro `{}` not expanded", name),
None => "proc macro not expanded".to_string(),
}
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: macro-error
//
// This diagnostic is shown for macro expansion errors.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroError {
pub file: HirFileId,
pub node: SyntaxNodePtr,
pub message: String,
}
impl Diagnostic for MacroError {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("macro-error")
}
fn message(&self) -> String {
self.message.clone()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
// Newly added and not very well-tested, might contain false positives.
true
}
}

View File

@ -35,12 +35,18 @@ use std::{iter, sync::Arc};
use arrayvec::ArrayVec;
use base_db::{CrateDisplayName, CrateId, Edition, FileId};
use diagnostics::{
InactiveCode, MacroError, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
UnresolvedModule, UnresolvedProcMacro,
};
use either::Either;
use hir_def::{
adt::{ReprKind, VariantData},
body::BodyDiagnostic,
expr::{BindingAnnotation, LabelId, Pat, PatId},
item_tree::ItemTreeNode,
lang_item::LangItemTarget,
nameres,
per_ns::PerNs,
resolver::{HasResolver, Resolver},
src::HasSource as _,
@ -50,11 +56,12 @@ use hir_def::{
LocalEnumVariantId, LocalFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId,
TypeParamId, UnionId,
};
use hir_expand::{diagnostics::DiagnosticSink, name::name, MacroDefKind};
use hir_expand::{name::name, MacroCallKind, MacroDefKind};
use hir_ty::{
autoderef,
consteval::ConstExt,
could_unify,
diagnostics_sink::DiagnosticSink,
method_resolution::{self, def_crates, TyFingerprint},
primitive::UintTy,
subst_prefix,
@ -65,11 +72,12 @@ use hir_ty::{
WhereClause,
};
use itertools::Itertools;
use nameres::diagnostics::DefDiagnosticKind;
use rustc_hash::FxHashSet;
use stdx::{format_to, impl_from};
use syntax::{
ast::{self, AttrsOwner, NameOwner},
AstNode, SmolStr,
AstNode, AstPtr, SmolStr, SyntaxKind, SyntaxNodePtr,
};
use tt::{Ident, Leaf, Literal, TokenTree};
@ -442,7 +450,137 @@ impl Module {
format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
});
let def_map = self.id.def_map(db.upcast());
def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
for diag in def_map.diagnostics() {
if diag.in_module != self.id.local_id {
// FIXME: This is accidentally quadratic.
continue;
}
match &diag.kind {
DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => {
let decl = declaration.to_node(db.upcast());
sink.push(UnresolvedModule {
file: declaration.file_id,
decl: AstPtr::new(&decl),
candidate: candidate.clone(),
})
}
DefDiagnosticKind::UnresolvedExternCrate { ast } => {
let item = ast.to_node(db.upcast());
sink.push(UnresolvedExternCrate {
file: ast.file_id,
item: AstPtr::new(&item),
});
}
DefDiagnosticKind::UnresolvedImport { ast, index } => {
let use_item = ast.to_node(db.upcast());
let hygiene = Hygiene::new(db.upcast(), ast.file_id);
let mut cur = 0;
let mut tree = None;
ModPath::expand_use_item(
db.upcast(),
InFile::new(ast.file_id, use_item),
&hygiene,
|_mod_path, use_tree, _is_glob, _alias| {
if cur == *index {
tree = Some(use_tree.clone());
}
cur += 1;
},
);
if let Some(tree) = tree {
sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
}
}
DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
let item = ast.to_node(db.upcast());
sink.push(InactiveCode {
file: ast.file_id,
node: AstPtr::new(&item).into(),
cfg: cfg.clone(),
opts: opts.clone(),
});
}
DefDiagnosticKind::UnresolvedProcMacro { ast } => {
let mut precise_location = None;
let (file, ast, name) = match ast {
MacroCallKind::FnLike { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
}
MacroCallKind::Derive { ast_id, derive_name, .. } => {
let node = ast_id.to_node(db.upcast());
// Compute the precise location of the macro name's token in the derive
// list.
// FIXME: This does not handle paths to the macro, but neither does the
// rest of r-a.
let derive_attrs =
node.attrs().filter_map(|attr| match attr.as_simple_call() {
Some((name, args)) if name == "derive" => Some(args),
_ => None,
});
'outer: for attr in derive_attrs {
let tokens =
attr.syntax().children_with_tokens().filter_map(|elem| {
match elem {
syntax::NodeOrToken::Node(_) => None,
syntax::NodeOrToken::Token(tok) => Some(tok),
}
});
for token in tokens {
if token.kind() == SyntaxKind::IDENT
&& token.text() == derive_name.as_str()
{
precise_location = Some(token.text_range());
break 'outer;
}
}
}
(
ast_id.file_id,
SyntaxNodePtr::from(AstPtr::new(&node)),
Some(derive_name.clone()),
)
}
};
sink.push(UnresolvedProcMacro {
file,
node: ast,
precise_location,
macro_name: name,
});
}
DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
let node = ast.to_node(db.upcast());
sink.push(UnresolvedMacroCall {
file: ast.file_id,
node: AstPtr::new(&node),
path: path.clone(),
});
}
DefDiagnosticKind::MacroError { ast, message } => {
let (file, ast) = match ast {
MacroCallKind::FnLike { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
}
MacroCallKind::Derive { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
}
};
sink.push(MacroError { file, node: ast, message: message.clone() });
}
}
}
for decl in self.declarations(db) {
match decl {
crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
@ -865,7 +1003,37 @@ impl Function {
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
let krate = self.module(db).id.krate();
hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
let source_map = db.body_with_source_map(self.id.into()).1;
for diag in source_map.diagnostics() {
match diag {
BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode {
file: node.file_id,
node: node.value.clone(),
cfg: cfg.clone(),
opts: opts.clone(),
}),
BodyDiagnostic::MacroError { node, message } => sink.push(MacroError {
file: node.file_id,
node: node.value.clone().into(),
message: message.to_string(),
}),
BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro {
file: node.file_id,
node: node.value.clone().into(),
precise_location: None,
macro_name: None,
}),
BodyDiagnostic::UnresolvedMacroCall { node, path } => {
sink.push(UnresolvedMacroCall {
file: node.file_id,
node: node.value.clone(),
path: path.clone(),
})
}
}
}
hir_ty::diagnostics::validate_module_item(db, krate, self.id.into(), sink);
hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
}

View File

@ -1,7 +1,6 @@
//! Defines `Body`: a lowered representation of bodies of functions, statics and
//! consts.
mod lower;
mod diagnostics;
#[cfg(test)]
mod tests;
pub mod scope;
@ -9,17 +8,16 @@ pub mod scope;
use std::{mem, ops::Index, sync::Arc};
use base_db::CrateId;
use cfg::CfgOptions;
use cfg::{CfgExpr, CfgOptions};
use drop_bomb::DropBomb;
use either::Either;
use hir_expand::{
ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, ExpandResult,
HirFileId, InFile, MacroDefId,
ast_id_map::AstIdMap, hygiene::Hygiene, AstId, ExpandResult, HirFileId, InFile, MacroDefId,
};
use la_arena::{Arena, ArenaMap};
use profile::Count;
use rustc_hash::FxHashMap;
use syntax::{ast, AstNode, AstPtr};
use syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
use crate::{
attr::{Attrs, RawAttrs},
@ -273,12 +271,20 @@ pub struct BodySourceMap {
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
/// the source map (since they're just as volatile).
diagnostics: Vec<diagnostics::BodyDiagnostic>,
diagnostics: Vec<BodyDiagnostic>,
}
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
pub struct SyntheticSyntax;
#[derive(Debug, Eq, PartialEq)]
pub enum BodyDiagnostic {
InactiveCode { node: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
MacroError { node: InFile<AstPtr<ast::MacroCall>>, message: String },
UnresolvedProcMacro { node: InFile<AstPtr<ast::MacroCall>> },
UnresolvedMacroCall { node: InFile<AstPtr<ast::MacroCall>>, path: ModPath },
}
impl Body {
pub(crate) fn body_with_source_map_query(
db: &dyn DefDatabase,
@ -416,9 +422,8 @@ impl BodySourceMap {
self.field_map.get(&src).cloned()
}
pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
for diag in &self.diagnostics {
diag.add_to(sink);
}
/// Get a reference to the body source map's diagnostics.
pub fn diagnostics(&self) -> &[BodyDiagnostic] {
&self.diagnostics
}
}

View File

@ -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());
}
}
}
}

View File

@ -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
}

View File

@ -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
}
"#,
);

View File

@ -1,227 +0,0 @@
//! Diagnostics produced by `hir_def`.
use std::any::Any;
use stdx::format_to;
use cfg::{CfgExpr, CfgOptions, DnfExpr};
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
use hir_expand::{HirFileId, InFile};
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
use crate::{db::DefDatabase, path::ModPath, DefWithBodyId};
pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
let source_map = db.body_with_source_map(owner).1;
source_map.add_diagnostics(db, sink);
}
// Diagnostic: unresolved-module
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
#[derive(Debug)]
pub struct UnresolvedModule {
pub file: HirFileId,
pub decl: AstPtr<ast::Module>,
pub candidate: String,
}
impl Diagnostic for UnresolvedModule {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-module")
}
fn message(&self) -> String {
"unresolved module".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.decl.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: unresolved-extern-crate
//
// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.
#[derive(Debug)]
pub struct UnresolvedExternCrate {
pub file: HirFileId,
pub item: AstPtr<ast::ExternCrate>,
}
impl Diagnostic for UnresolvedExternCrate {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-extern-crate")
}
fn message(&self) -> String {
"unresolved extern crate".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.item.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: unresolved-import
//
// This diagnostic is triggered if rust-analyzer is unable to discover imported module.
#[derive(Debug)]
pub struct UnresolvedImport {
pub file: HirFileId,
pub node: AstPtr<ast::UseTree>,
}
impl Diagnostic for UnresolvedImport {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-import")
}
fn message(&self) -> String {
"unresolved import".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
// This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
// - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
// - proc macros and/or proc macro generated code
true
}
}
// Diagnostic: unresolved-macro-call
//
// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
// macro in a macro invocation.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedMacroCall {
pub file: HirFileId,
pub node: AstPtr<ast::MacroCall>,
pub path: ModPath,
}
impl Diagnostic for UnresolvedMacroCall {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-macro-call")
}
fn message(&self) -> String {
format!("unresolved macro `{}!`", self.path)
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
true
}
}
// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct InactiveCode {
pub file: HirFileId,
pub node: SyntaxNodePtr,
pub cfg: CfgExpr,
pub opts: CfgOptions,
}
impl Diagnostic for InactiveCode {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("inactive-code")
}
fn message(&self) -> String {
let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
let mut buf = "code is inactive due to #[cfg] directives".to_string();
if let Some(inactive) = inactive {
format_to!(buf, ": {}", inactive);
}
buf
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: unresolved-proc-macro
//
// This diagnostic is shown when a procedural macro can not be found. This usually means that
// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
// but can also indicate project setup problems.
//
// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct UnresolvedProcMacro {
pub file: HirFileId,
pub node: SyntaxNodePtr,
/// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
/// to use instead.
pub precise_location: Option<TextRange>,
pub macro_name: Option<String>,
}
impl Diagnostic for UnresolvedProcMacro {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-proc-macro")
}
fn message(&self) -> String {
match &self.macro_name {
Some(name) => format!("proc macro `{}` not expanded", name),
None => "proc macro not expanded".to_string(),
}
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
}
// Diagnostic: macro-error
//
// This diagnostic is shown for macro expansion errors.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MacroError {
pub file: HirFileId,
pub node: SyntaxNodePtr,
pub message: String,
}
impl Diagnostic for MacroError {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("macro-error")
}
fn message(&self) -> String {
self.message.clone()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
// Newly added and not very well-tested, might contain false positives.
true
}
}

View File

@ -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 {

View File

@ -47,18 +47,19 @@
//! path and, upon success, we run macro expansion and "collect module" phase on
//! the result
pub mod diagnostics;
mod collector;
mod mod_resolution;
mod path_resolution;
mod proc_macro;
#[cfg(test)]
mod tests;
mod proc_macro;
use std::sync::Arc;
use base_db::{CrateId, Edition, FileId};
use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId};
use hir_expand::{name::Name, InFile, MacroDefId};
use la_arena::Arena;
use profile::Count;
use rustc_hash::FxHashMap;
@ -254,15 +255,6 @@ impl DefMap {
}
}
pub fn add_diagnostics(
&self,
db: &dyn DefDatabase,
module: LocalModuleId,
sink: &mut DiagnosticSink,
) {
self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
}
pub fn modules_for_file(&self, file_id: FileId) -> impl Iterator<Item = LocalModuleId> + '_ {
self.modules
.iter()
@ -448,6 +440,11 @@ impl DefMap {
module.scope.shrink_to_fit();
}
}
/// Get a reference to the def map's diagnostics.
pub fn diagnostics(&self) -> &[DefDiagnostic] {
self.diagnostics.as_slice()
}
}
impl ModuleData {
@ -471,236 +468,3 @@ pub enum ModuleSource {
Module(ast::Module),
BlockExpr(ast::BlockExpr),
}
mod diagnostics {
use cfg::{CfgExpr, CfgOptions};
use hir_expand::diagnostics::DiagnosticSink;
use hir_expand::hygiene::Hygiene;
use hir_expand::{InFile, MacroCallKind};
use syntax::ast::AttrsOwner;
use syntax::{ast, AstNode, AstPtr, SyntaxKind, SyntaxNodePtr};
use crate::path::ModPath;
use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
#[derive(Debug, PartialEq, Eq)]
enum DiagnosticKind {
UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
UnresolvedProcMacro { ast: MacroCallKind },
UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
MacroError { ast: MacroCallKind, message: String },
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct DefDiagnostic {
in_module: LocalModuleId,
kind: DiagnosticKind,
}
impl DefDiagnostic {
pub(super) fn unresolved_module(
container: LocalModuleId,
declaration: AstId<ast::Module>,
candidate: String,
) -> Self {
Self {
in_module: container,
kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
}
}
pub(super) fn unresolved_extern_crate(
container: LocalModuleId,
declaration: AstId<ast::ExternCrate>,
) -> Self {
Self {
in_module: container,
kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
}
}
pub(super) fn unresolved_import(
container: LocalModuleId,
ast: AstId<ast::Use>,
index: usize,
) -> Self {
Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
}
pub(super) fn unconfigured_code(
container: LocalModuleId,
ast: AstId<ast::Item>,
cfg: CfgExpr,
opts: CfgOptions,
) -> Self {
Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
}
pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
Self { in_module: container, kind: DiagnosticKind::UnresolvedProcMacro { ast } }
}
pub(super) fn macro_error(
container: LocalModuleId,
ast: MacroCallKind,
message: String,
) -> Self {
Self { in_module: container, kind: DiagnosticKind::MacroError { ast, message } }
}
pub(super) fn unresolved_macro_call(
container: LocalModuleId,
ast: AstId<ast::MacroCall>,
path: ModPath,
) -> Self {
Self { in_module: container, kind: DiagnosticKind::UnresolvedMacroCall { ast, path } }
}
pub(super) fn add_to(
&self,
db: &dyn DefDatabase,
target_module: LocalModuleId,
sink: &mut DiagnosticSink,
) {
if self.in_module != target_module {
return;
}
match &self.kind {
DiagnosticKind::UnresolvedModule { declaration, candidate } => {
let decl = declaration.to_node(db.upcast());
sink.push(UnresolvedModule {
file: declaration.file_id,
decl: AstPtr::new(&decl),
candidate: candidate.clone(),
})
}
DiagnosticKind::UnresolvedExternCrate { ast } => {
let item = ast.to_node(db.upcast());
sink.push(UnresolvedExternCrate {
file: ast.file_id,
item: AstPtr::new(&item),
});
}
DiagnosticKind::UnresolvedImport { ast, index } => {
let use_item = ast.to_node(db.upcast());
let hygiene = Hygiene::new(db.upcast(), ast.file_id);
let mut cur = 0;
let mut tree = None;
ModPath::expand_use_item(
db,
InFile::new(ast.file_id, use_item),
&hygiene,
|_mod_path, use_tree, _is_glob, _alias| {
if cur == *index {
tree = Some(use_tree.clone());
}
cur += 1;
},
);
if let Some(tree) = tree {
sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
}
}
DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
let item = ast.to_node(db.upcast());
sink.push(InactiveCode {
file: ast.file_id,
node: AstPtr::new(&item).into(),
cfg: cfg.clone(),
opts: opts.clone(),
});
}
DiagnosticKind::UnresolvedProcMacro { ast } => {
let mut precise_location = None;
let (file, ast, name) = match ast {
MacroCallKind::FnLike { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None)
}
MacroCallKind::Derive { ast_id, derive_name, .. } => {
let node = ast_id.to_node(db.upcast());
// Compute the precise location of the macro name's token in the derive
// list.
// FIXME: This does not handle paths to the macro, but neither does the
// rest of r-a.
let derive_attrs =
node.attrs().filter_map(|attr| match attr.as_simple_call() {
Some((name, args)) if name == "derive" => Some(args),
_ => None,
});
'outer: for attr in derive_attrs {
let tokens =
attr.syntax().children_with_tokens().filter_map(|elem| {
match elem {
syntax::NodeOrToken::Node(_) => None,
syntax::NodeOrToken::Token(tok) => Some(tok),
}
});
for token in tokens {
if token.kind() == SyntaxKind::IDENT
&& token.text() == derive_name.as_str()
{
precise_location = Some(token.text_range());
break 'outer;
}
}
}
(
ast_id.file_id,
SyntaxNodePtr::from(AstPtr::new(&node)),
Some(derive_name.clone()),
)
}
};
sink.push(UnresolvedProcMacro {
file,
node: ast,
precise_location,
macro_name: name,
});
}
DiagnosticKind::UnresolvedMacroCall { ast, path } => {
let node = ast.to_node(db.upcast());
sink.push(UnresolvedMacroCall {
file: ast.file_id,
node: AstPtr::new(&node),
path: path.clone(),
});
}
DiagnosticKind::MacroError { ast, message } => {
let (file, ast) = match ast {
MacroCallKind::FnLike { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
}
MacroCallKind::Derive { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
}
};
sink.push(MacroError { file, node: ast, message: message.clone() });
}
}
}
}
}

View File

@ -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;

View File

@ -0,0 +1,90 @@
//! Diagnostics emitted during DefMap construction.
use cfg::{CfgExpr, CfgOptions};
use hir_expand::MacroCallKind;
use syntax::ast;
use crate::{nameres::LocalModuleId, path::ModPath, AstId};
#[derive(Debug, PartialEq, Eq)]
pub enum DefDiagnosticKind {
UnresolvedModule { ast: AstId<ast::Module>, candidate: String },
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
UnconfiguredCode { ast: AstId<ast::Item>, cfg: CfgExpr, opts: CfgOptions },
UnresolvedProcMacro { ast: MacroCallKind },
UnresolvedMacroCall { ast: AstId<ast::MacroCall>, path: ModPath },
MacroError { ast: MacroCallKind, message: String },
}
#[derive(Debug, PartialEq, Eq)]
pub struct DefDiagnostic {
pub in_module: LocalModuleId,
pub kind: DefDiagnosticKind,
}
impl DefDiagnostic {
pub(super) fn unresolved_module(
container: LocalModuleId,
declaration: AstId<ast::Module>,
candidate: String,
) -> Self {
Self {
in_module: container,
kind: DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate },
}
}
pub(super) fn unresolved_extern_crate(
container: LocalModuleId,
declaration: AstId<ast::ExternCrate>,
) -> Self {
Self {
in_module: container,
kind: DefDiagnosticKind::UnresolvedExternCrate { ast: declaration },
}
}
pub(super) fn unresolved_import(
container: LocalModuleId,
ast: AstId<ast::Use>,
index: usize,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { ast, index } }
}
pub(super) fn unconfigured_code(
container: LocalModuleId,
ast: AstId<ast::Item>,
cfg: CfgExpr,
opts: CfgOptions,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
}
pub(super) fn unresolved_proc_macro(container: LocalModuleId, ast: MacroCallKind) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast } }
}
pub(super) fn macro_error(
container: LocalModuleId,
ast: MacroCallKind,
message: String,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, message } }
}
pub(super) fn unresolved_macro_call(
container: LocalModuleId,
ast: AstId<ast::MacroCall>,
path: ModPath,
) -> Self {
Self { in_module: container, kind: DefDiagnosticKind::UnresolvedMacroCall { ast, path } }
}
}

View File

@ -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
"#,
);
}

View File

@ -71,7 +71,7 @@ impl ModPath {
}
/// Calls `cb` with all paths, represented by this use item.
pub(crate) fn expand_use_item(
pub fn expand_use_item(
db: &dyn DefDatabase,
item_src: InFile<ast::Use>,
hygiene: &Hygiene,

View File

@ -5,19 +5,20 @@ use std::{
sync::{Arc, Mutex},
};
use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, Upcast};
use base_db::{
salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
};
use base_db::{AnchoredPath, SourceDatabase};
use hir_expand::diagnostics::Diagnostic;
use hir_expand::diagnostics::DiagnosticSinkBuilder;
use hir_expand::{db::AstDatabase, InFile};
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
use syntax::{algo, ast, AstNode, TextRange, TextSize};
use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
use test_utils::extract_annotations;
use crate::{
body::BodyDiagnostic,
db::DefDatabase,
nameres::{DefMap, ModuleSource},
nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
src::HasSource,
LocalModuleId, Lookup, ModuleDefId, ModuleId,
};
@ -262,19 +263,70 @@ impl TestDB {
.collect()
}
pub(crate) fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
let crate_graph = self.crate_graph();
for krate in crate_graph.iter() {
let crate_def_map = self.crate_def_map(krate);
let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
for (module_id, module) in crate_def_map.modules() {
crate_def_map.add_diagnostics(self, module_id, &mut sink);
for diag in crate_def_map.diagnostics() {
let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
DefDiagnosticKind::UnresolvedModule { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
}
DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
}
DefDiagnosticKind::UnresolvedImport { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedImport")
}
DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
}
DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
(ast.to_node(self.upcast()), "UnresolvedProcMacro")
}
DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
let node = ast.to_node(self.upcast());
(InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
}
DefDiagnosticKind::MacroError { ast, message } => {
(ast.to_node(self.upcast()), message.as_str())
}
};
let frange = node.as_ref().original_file_range(self);
cb(frange, message.to_string())
}
for (_module_id, module) in crate_def_map.modules() {
for decl in module.scope.declarations() {
if let ModuleDefId::FunctionId(it) = decl {
let source_map = self.body_with_source_map(it.into()).1;
source_map.add_diagnostics(self, &mut sink);
for diag in source_map.diagnostics() {
let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
BodyDiagnostic::InactiveCode { node, .. } => {
(node.clone().map(|it| it.into()), "InactiveCode")
}
BodyDiagnostic::MacroError { node, message } => {
(node.clone().map(|it| it.into()), message.as_str())
}
BodyDiagnostic::UnresolvedProcMacro { node } => {
(node.clone().map(|it| it.into()), "UnresolvedProcMacro")
}
BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
(node.clone().map(|it| it.into()), "UnresolvedMacroCall")
}
};
let root = self.parse_or_expand(ptr.file_id).unwrap();
let node = ptr.map(|ptr| ptr.to_node(&root));
let frange = node.as_ref().original_file_range(self);
cb(frange, message.to_string())
}
}
}
}
@ -287,14 +339,7 @@ impl TestDB {
assert!(!annotations.is_empty());
let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
db.diagnostics(|d| {
let src = d.display_source();
let root = db.parse_or_expand(src.file_id).unwrap();
let node = src.map(|ptr| ptr.to_node(&root));
let frange = node.as_ref().original_file_range(db);
let message = d.message();
db.diagnostics(&mut |frange, message| {
actual.entry(frange.file_id).or_default().push((frange.range, message));
});
@ -319,7 +364,7 @@ impl TestDB {
assert!(annotations.is_empty());
let mut has_diagnostics = false;
db.diagnostics(|_| {
db.diagnostics(&mut |_, _| {
has_diagnostics = true;
});

View File

@ -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 =

View File

@ -8,7 +8,6 @@ pub mod db;
pub mod ast_id_map;
pub mod name;
pub mod hygiene;
pub mod diagnostics;
pub mod builtin_derive;
pub mod builtin_macro;
pub mod proc_macro;
@ -108,7 +107,7 @@ impl HirFileId {
HirFileIdRepr::FileId(_) => None,
HirFileIdRepr::MacroFile(macro_file) => {
let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
Some(loc.kind.node(db))
Some(loc.kind.to_node(db))
}
}
}
@ -153,7 +152,7 @@ impl HirFileId {
HirFileIdRepr::MacroFile(macro_file) => {
let loc: MacroCallLoc = db.lookup_intern_macro(macro_file.macro_call_id);
let item = match loc.def.kind {
MacroDefKind::BuiltInDerive(..) => loc.kind.node(db),
MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
_ => return None,
};
Some(item.with_value(ast::Item::cast(item.value.clone())?))
@ -269,7 +268,7 @@ impl MacroCallKind {
}
}
fn node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
match self {
MacroCallKind::FnLike { ast_id, .. } => {
ast_id.with_value(ast_id.to_node(db).syntax().clone())

View File

@ -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,
};

View File

@ -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 {

View File

@ -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,
};

View File

@ -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> {

View File

@ -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);

View File

@ -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)]

View File

@ -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;

View File

@ -299,10 +299,10 @@ fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist {
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
use expect_test::Expect;
use ide_assists::AssistResolveStrategy;
use stdx::trim_indent;
use test_utils::assert_eq_text;
use test_utils::{assert_eq_text, extract_annotations};
use crate::{fixture, DiagnosticsConfig};
@ -396,26 +396,51 @@ mod tests {
expect.assert_debug_eq(&diagnostics)
}
pub(crate) fn check_diagnostics(ra_fixture: &str) {
let (analysis, file_id) = fixture::file(ra_fixture);
let diagnostics = analysis
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
.unwrap();
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let actual = diagnostics.into_iter().map(|d| (d.range, d.message)).collect::<Vec<_>>();
assert_eq!(expected, actual);
}
#[test]
fn test_unresolved_macro_range() {
check_expect(
r#"foo::bar!(92);"#,
expect![[r#"
[
Diagnostic {
message: "unresolved macro `foo::bar!`",
range: 5..8,
severity: Error,
fixes: None,
unused: false,
code: Some(
DiagnosticCode(
"unresolved-macro-call",
),
),
},
]
"#]],
check_diagnostics(
r#"
foo::bar!(92);
//^^^ unresolved macro `foo::bar!`
"#,
);
}
#[test]
fn unresolved_import_in_use_tree() {
// Only the relevant part of a nested `use` item should be highlighted.
check_diagnostics(
r#"
use does_exist::{Exists, DoesntExist};
//^^^^^^^^^^^ unresolved import
use {does_not_exist::*, does_exist};
//^^^^^^^^^^^^^^^^^ unresolved import
use does_not_exist::{
a,
//^ unresolved import
b,
//^ unresolved import
c,
//^ unresolved import
};
mod does_exist {
pub struct Exists;
}
"#,
);
}