9249: internal: remove def-level diagnostics tests r=matklad a=matklad

bors r+
🤖

Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2021-06-13 15:46:09 +00:00 committed by GitHub
commit 7bff76d8ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 398 additions and 562 deletions

View File

@ -5,11 +5,10 @@
//! be expressed in terms of hir types themselves.
use std::any::Any;
use cfg::{CfgExpr, CfgOptions, DnfExpr};
use cfg::{CfgExpr, CfgOptions};
use either::Either;
use hir_def::path::ModPath;
use hir_expand::{name::Name, HirFileId, InFile};
use stdx::format_to;
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
pub use crate::diagnostics_sink::{
@ -37,7 +36,10 @@ fn from(d: $diag) -> AnyDiagnostic {
UnresolvedExternCrate,
UnresolvedImport,
UnresolvedMacroCall,
UnresolvedProcMacro,
MacroError,
MissingFields,
InactiveCode,
];
#[derive(Debug)]
@ -62,108 +64,28 @@ pub struct UnresolvedMacroCall {
pub path: ModPath,
}
// 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 node: InFile<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,
pub node: InFile<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 node: InFile<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
}
}
#[derive(Debug)]
pub struct UnimplementedBuiltinMacro {
pub file: HirFileId,

View File

@ -506,20 +506,22 @@ pub fn diagnostics(
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(),
});
acc.push(
InactiveCode {
node: ast.with_value(AstPtr::new(&item).into()),
cfg: cfg.clone(),
opts: opts.clone(),
}
.into(),
);
}
DefDiagnosticKind::UnresolvedProcMacro { ast } => {
let mut precise_location = None;
let (file, ast, name) = match ast {
let (node, 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)
(ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))), None)
}
MacroCallKind::Derive { ast_id, derive_name, .. } => {
let node = ast_id.to_node(db.upcast());
@ -552,8 +554,7 @@ pub fn diagnostics(
}
(
ast_id.file_id,
SyntaxNodePtr::from(AstPtr::new(&node)),
ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))),
Some(derive_name.clone()),
)
}
@ -564,18 +565,14 @@ pub fn diagnostics(
|| panic!("cannot find attribute #{}", invoc_attr_index),
);
(
ast_id.file_id,
SyntaxNodePtr::from(AstPtr::new(&attr)),
ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&attr))),
Some(attr_name.clone()),
)
}
};
sink.push(UnresolvedProcMacro {
file,
node: ast,
precise_location,
macro_name: name,
});
acc.push(
UnresolvedProcMacro { node, precise_location, macro_name: name }.into(),
);
}
DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
@ -590,19 +587,19 @@ pub fn diagnostics(
}
DefDiagnosticKind::MacroError { ast, message } => {
let (file, ast) = match ast {
let node = match ast {
MacroCallKind::FnLike { ast_id, .. } => {
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
}
MacroCallKind::Derive { ast_id, .. }
| MacroCallKind::Attr { ast_id, .. } => {
// FIXME: point to the attribute instead, this creates very large diagnostics
let node = ast_id.to_node(db.upcast());
(ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)))
ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
}
};
sink.push(MacroError { file, node: ast, message: message.clone() });
acc.push(MacroError { node, message: message.clone() }.into());
}
DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
@ -1045,23 +1042,25 @@ pub fn diagnostics(
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::InactiveCode { node, cfg, opts } => acc.push(
InactiveCode { node: node.clone(), cfg: cfg.clone(), opts: opts.clone() }
.into(),
),
BodyDiagnostic::MacroError { node, message } => acc.push(
MacroError {
node: node.clone().map(|it| it.into()),
message: message.to_string(),
}
.into(),
),
BodyDiagnostic::UnresolvedProcMacro { node } => acc.push(
UnresolvedProcMacro {
node: node.clone().map(|it| it.into()),
precise_location: None,
macro_name: None,
}
.into(),
),
BodyDiagnostic::UnresolvedMacroCall { node, path } => acc.push(
UnresolvedMacroCall { macro_call: node.clone(), path: path.clone() }.into(),
),

View File

@ -3,7 +3,7 @@
use base_db::{fixture::WithFixture, SourceDatabase};
use expect_test::Expect;
use crate::{test_db::TestDB, ModuleDefId};
use crate::ModuleDefId;
use super::*;
@ -28,11 +28,6 @@ fn lower(ra_fixture: &str) -> Arc<Body> {
db.body(fn_def.unwrap().into())
}
fn check_diagnostics(ra_fixture: &str) {
let db: TestDB = TestDB::with_files(ra_fixture);
db.check_diagnostics();
}
fn block_def_map_at(ra_fixture: &str) -> String {
let (db, position) = crate::test_db::TestDB::with_position(ra_fixture);
@ -57,7 +52,7 @@ fn check_at(ra_fixture: &str, expect: Expect) {
fn your_stack_belongs_to_me() {
cov_mark::check!(your_stack_belongs_to_me);
lower(
"
r#"
macro_rules! n_nuple {
($e:tt) => ();
($($rest:tt)*) => {{
@ -65,7 +60,7 @@ macro_rules! n_nuple {
}};
}
fn main() { n_nuple!(1,2,3); }
",
"#,
);
}
@ -73,7 +68,7 @@ macro_rules! n_nuple {
fn macro_resolve() {
// Regression test for a path resolution bug introduced with inner item handling.
lower(
r"
r#"
macro_rules! vec {
() => { () };
($elem:expr; $n:expr) => { () };
@ -84,140 +79,6 @@ fn outer() {
let _ = vec![FileSet::default(); self.len()];
}
}
",
"#,
);
}
#[test]
fn cfg_diagnostics() {
check_diagnostics(
r"
fn f() {
// The three g̶e̶n̶d̶e̶r̶s̶ statements:
#[cfg(a)] fn f() {} // Item statement
//^^^^^^^^^^^^^^^^^^^ InactiveCode
#[cfg(a)] {} // Expression statement
//^^^^^^^^^^^^ InactiveCode
#[cfg(a)] let x = 0; // let statement
//^^^^^^^^^^^^^^^^^^^^ InactiveCode
abc(#[cfg(a)] 0);
//^^^^^^^^^^^ InactiveCode
let x = Struct {
#[cfg(a)] f: 0,
//^^^^^^^^^^^^^^ InactiveCode
};
match () {
() => (),
#[cfg(a)] () => (),
//^^^^^^^^^^^^^^^^^^ InactiveCode
}
#[cfg(a)] 0 // Trailing expression of block
//^^^^^^^^^^^ InactiveCode
}
",
);
}
#[test]
fn macro_diag_builtin() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! env {}
#[rustc_builtin_macro]
macro_rules! include {}
#[rustc_builtin_macro]
macro_rules! compile_error {}
#[rustc_builtin_macro]
macro_rules! format_args {
() => {}
}
fn f() {
// Test a handful of built-in (eager) macros:
include!(invalid);
//^^^^^^^^^^^^^^^^^ could not convert tokens
include!("does not exist");
//^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist`
env!(invalid);
//^^^^^^^^^^^^^ could not convert tokens
env!("OUT_DIR");
//^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
compile_error!("compile_error works");
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works
// Lazy:
format_args!();
//^^^^^^^^^^^^^^ no rule matches input tokens
}
"#,
);
}
#[test]
fn macro_rules_diag() {
check_diagnostics(
r#"
macro_rules! m {
() => {};
}
fn f() {
m!();
m!(hi);
//^^^^^^ leftover tokens
}
"#,
);
}
#[test]
fn unresolved_macro_diag() {
check_diagnostics(
r#"
fn f() {
m!();
//^^^^ UnresolvedMacroCall
}
"#,
);
}
#[test]
fn dollar_crate_in_builtin_macro() {
check_diagnostics(
r#"
#[macro_export]
#[rustc_builtin_macro]
macro_rules! format_args {}
#[macro_export]
macro_rules! arg {
() => {}
}
#[macro_export]
macro_rules! outer {
() => {
$crate::format_args!( "", $crate::arg!(1) )
};
}
fn f() {
outer!();
//^^^^^^^^ leftover tokens
}
"#,
)
}

View File

@ -2,7 +2,6 @@
mod incremental;
mod macros;
mod mod_resolution;
mod diagnostics;
mod primitives;
use std::sync::Arc;

View File

@ -1,118 +0,0 @@
use base_db::fixture::WithFixture;
use crate::test_db::TestDB;
fn check_diagnostics(ra_fixture: &str) {
let db: TestDB = TestDB::with_files(ra_fixture);
db.check_diagnostics();
}
fn check_no_diagnostics(ra_fixture: &str) {
let db: TestDB = TestDB::with_files(ra_fixture);
db.check_no_diagnostics();
}
#[test]
fn inactive_item() {
// Additional tests in `cfg` crate. This only tests disabled cfgs.
check_diagnostics(
r#"
//- /lib.rs
#[cfg(no)] pub fn f() {}
//^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
#[cfg(no)] #[cfg(no2)] mod m;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
#[cfg(all(not(a), b))] enum E {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
#[cfg(feature = "std")] use std;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
"#,
);
}
/// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
#[test]
fn inactive_via_cfg_attr() {
cov_mark::check!(cfg_attr_active);
check_diagnostics(
r#"
//- /lib.rs
#[cfg_attr(not(never), cfg(no))] fn f() {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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() {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
"#,
);
}
#[test]
fn builtin_macro_fails_expansion() {
check_diagnostics(
r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }
include!("doesntexist");
//^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist`
"#,
);
}
#[test]
fn include_macro_should_allow_empty_content() {
check_no_diagnostics(
r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }
include!("bar.rs");
//- /bar.rs
// empty
"#,
);
}
#[test]
fn good_out_dir_diagnostic() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! include { () => {} }
#[rustc_builtin_macro]
macro_rules! env { () => {} }
#[rustc_builtin_macro]
macro_rules! concat { () => {} }
include!(concat!(env!("OUT_DIR"), "/out.rs"));
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
"#,
);
}
#[test]
fn register_attr_and_tool() {
cov_mark::check!(register_attr);
cov_mark::check!(register_tool);
check_no_diagnostics(
r#"
#![register_tool(tool)]
#![register_attr(attr)]
#[tool::path]
#[attr]
struct S;
"#,
);
// NB: we don't currently emit diagnostics here
}

View File

@ -6,19 +6,16 @@
};
use base_db::{
salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition,
SourceDatabase, Upcast,
};
use base_db::{AnchoredPath, SourceDatabase};
use hir_expand::{db::AstDatabase, InFile};
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
use test_utils::extract_annotations;
use syntax::{algo, ast, AstNode};
use crate::{
body::BodyDiagnostic,
db::DefDatabase,
nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
nameres::{DefMap, ModuleSource},
src::HasSource,
LocalModuleId, Lookup, ModuleDefId, ModuleId,
};
@ -245,145 +242,4 @@ pub(crate) fn log_executed(&self, f: impl FnOnce()) -> Vec<String> {
})
.collect()
}
pub(crate) fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> {
let mut files = Vec::new();
let crate_graph = self.crate_graph();
for krate in crate_graph.iter() {
let crate_def_map = self.crate_def_map(krate);
for (module_id, _) in crate_def_map.modules() {
let file_id = crate_def_map[module_id].origin.file_id();
files.extend(file_id)
}
}
assert!(!files.is_empty());
files
.into_iter()
.filter_map(|file_id| {
let text = self.file_text(file_id);
let annotations = extract_annotations(&text);
if annotations.is_empty() {
return None;
}
Some((file_id, annotations))
})
.collect()
}
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);
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 { id, .. } => {
let item_tree = id.item_tree(self.upcast());
let import = &item_tree[id.value];
let node = InFile::new(id.file_id(), import.ast_id).to_node(self.upcast());
(InFile::new(id.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())
}
DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
let node = ast.to_node(self.upcast());
(
InFile::new(ast.file_id, node.syntax().clone()),
"UnimplementedBuiltinMacro",
)
}
};
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;
for diag in source_map.diagnostics() {
let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
BodyDiagnostic::InactiveCode { node, .. } => {
(node.clone().map(|it| it), "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())
}
}
}
}
}
}
pub(crate) fn check_diagnostics(&self) {
let db: &TestDB = self;
let annotations = db.extract_annotations();
assert!(!annotations.is_empty());
let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
db.diagnostics(&mut |frange, message| {
actual.entry(frange.file_id).or_default().push((frange.range, message));
});
for (file_id, diags) in actual.iter_mut() {
diags.sort_by_key(|it| it.0.start());
let text = db.file_text(*file_id);
// For multiline spans, place them on line start
for (range, content) in diags {
if text[*range].contains('\n') {
*range = TextRange::new(range.start(), range.start() + TextSize::from(1));
*content = format!("... {}", content);
}
}
}
assert_eq!(annotations, actual);
}
pub(crate) fn check_no_diagnostics(&self) {
let db: &TestDB = self;
let annotations = db.extract_annotations();
assert!(annotations.is_empty());
let mut has_diagnostics = false;
db.diagnostics(&mut |_, _| {
has_diagnostics = true;
});
assert!(!has_diagnostics);
}
}

View File

@ -8,6 +8,9 @@
mod unresolved_extern_crate;
mod unresolved_import;
mod unresolved_macro_call;
mod unresolved_proc_macro;
mod macro_error;
mod inactive_code;
mod missing_fields;
mod fixes;
@ -67,6 +70,11 @@ fn experimental(mut self) -> Diagnostic {
self
}
fn severity(mut self, severity: Severity) -> Diagnostic {
self.severity = severity;
self
}
fn error(range: TextRange, message: String) -> Self {
Self {
message,
@ -164,22 +172,6 @@ pub(crate) fn diagnostics(
.on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
})
.on::<hir::diagnostics::InactiveCode, _>(|d| {
// If there's inactive code somewhere in a macro, don't propagate to the call-site.
if d.display_source().file_id.expansion_info(db).is_some() {
return;
}
// Override severity and mark as unused.
res.borrow_mut().push(
Diagnostic::hint(
sema.diagnostics_display_range(d.display_source()).range,
d.message(),
)
.with_unused(true)
.with_code(Some(d.code())),
);
})
.on::<UnlinkedFile, _>(|d| {
// Limit diagnostic to the first few characters in the file. This matches how VS Code
// renders it with the full span, but on other editors, and is less invasive.
@ -193,16 +185,6 @@ pub(crate) fn diagnostics(
.with_code(Some(d.code())),
);
})
.on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
// Use more accurate position if available.
let display_range = d
.precise_location
.unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
// FIXME: it would be nice to tell the user whether proc macros are currently disabled
res.borrow_mut()
.push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
})
.on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
let display_range = sema.diagnostics_display_range(d.display_source()).range;
res.borrow_mut()
@ -246,7 +228,14 @@ pub(crate) fn diagnostics(
AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
Some(it) => it,
None => continue,
}
};
if let Some(code) = d.code {
if ctx.config.disabled.contains(code.as_str()) {
@ -451,7 +440,13 @@ pub(crate) fn check_expect(ra_fixture: &str, expect: Expect) {
expect.assert_debug_eq(&diagnostics)
}
#[track_caller]
pub(crate) fn check_diagnostics(ra_fixture: &str) {
check_diagnostics_with_inactive_code(ra_fixture, false)
}
#[track_caller]
pub(crate) fn check_diagnostics_with_inactive_code(ra_fixture: &str, with_inactive_code: bool) {
let (analysis, file_id) = fixture::file(ra_fixture);
let diagnostics = analysis
.diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
@ -460,7 +455,7 @@ pub(crate) fn check_diagnostics(ra_fixture: &str) {
let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
let mut actual = diagnostics
.into_iter()
.filter(|d| d.code != Some(DiagnosticCode("inactive-code")))
.filter(|d| d.code != Some(DiagnosticCode("inactive-code")) || with_inactive_code)
.map(|d| (d.range, d.message))
.collect::<Vec<_>>();
actual.sort_by_key(|(range, _)| range.start());

View File

@ -0,0 +1,117 @@
use cfg::DnfExpr;
use stdx::format_to;
use crate::{
diagnostics::{Diagnostic, DiagnosticsContext},
Severity,
};
// Diagnostic: inactive-code
//
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
pub(super) fn inactive_code(
ctx: &DiagnosticsContext<'_>,
d: &hir::InactiveCode,
) -> Option<Diagnostic> {
// If there's inactive code somewhere in a macro, don't propagate to the call-site.
if d.node.file_id.expansion_info(ctx.sema.db).is_some() {
return None;
}
let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
let mut message = "code is inactive due to #[cfg] directives".to_string();
if let Some(inactive) = inactive {
format_to!(message, ": {}", inactive);
}
let res = Diagnostic::new(
"inactive-code",
message,
ctx.sema.diagnostics_display_range(d.node.clone()).range,
)
.severity(Severity::WeakWarning)
.with_unused(true);
Some(res)
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics_with_inactive_code;
#[test]
fn cfg_diagnostics() {
check_diagnostics_with_inactive_code(
r#"
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
#[cfg(a)] {} // Expression statement
//^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
#[cfg(a)] let x = 0; // let statement
//^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
abc(#[cfg(a)] 0);
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
let x = Struct {
#[cfg(a)] f: 0,
//^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
};
match () {
() => (),
#[cfg(a)] () => (),
//^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
}
#[cfg(a)] 0 // Trailing expression of block
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
}
"#,
true,
);
}
#[test]
fn inactive_item() {
// Additional tests in `cfg` crate. This only tests disabled cfgs.
check_diagnostics_with_inactive_code(
r#"
#[cfg(no)] pub fn f() {}
//^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
#[cfg(no)] #[cfg(no2)] mod m;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
#[cfg(all(not(a), b))] enum E {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
#[cfg(feature = "std")] use std;
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
"#,
true,
);
}
/// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
#[test]
fn inactive_via_cfg_attr() {
cov_mark::check!(cfg_attr_active);
check_diagnostics_with_inactive_code(
r#"
#[cfg_attr(not(never), cfg(no))] fn f() {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
#[cfg_attr(not(never), cfg(not(no)))] fn f() {}
#[cfg_attr(never, cfg(no))] fn g() {}
#[cfg_attr(not(never), inline, cfg(no))] fn h() {}
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
"#,
true,
);
}
}

View File

@ -0,0 +1,163 @@
use crate::diagnostics::{Diagnostic, DiagnosticsContext};
// Diagnostic: macro-error
//
// This diagnostic is shown for macro expansion errors.
pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
Diagnostic::new(
"macro-error",
d.message.clone(),
ctx.sema.diagnostics_display_range(d.node.clone()).range,
)
.experimental()
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::{check_diagnostics, check_no_diagnostics};
#[test]
fn builtin_macro_fails_expansion() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! include { () => {} }
include!("doesntexist");
//^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist`
"#,
);
}
#[test]
fn include_macro_should_allow_empty_content() {
check_diagnostics(
r#"
//- /lib.rs
#[rustc_builtin_macro]
macro_rules! include { () => {} }
include!("foo/bar.rs");
//- /foo/bar.rs
// empty
"#,
);
}
#[test]
fn good_out_dir_diagnostic() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! include { () => {} }
#[rustc_builtin_macro]
macro_rules! env { () => {} }
#[rustc_builtin_macro]
macro_rules! concat { () => {} }
include!(concat!(env!("OUT_DIR"), "/out.rs"));
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
"#,
);
}
#[test]
fn register_attr_and_tool() {
cov_mark::check!(register_attr);
cov_mark::check!(register_tool);
check_no_diagnostics(
r#"
#![register_tool(tool)]
#![register_attr(attr)]
#[tool::path]
#[attr]
struct S;
"#,
);
// NB: we don't currently emit diagnostics here
}
#[test]
fn macro_diag_builtin() {
check_diagnostics(
r#"
#[rustc_builtin_macro]
macro_rules! env {}
#[rustc_builtin_macro]
macro_rules! include {}
#[rustc_builtin_macro]
macro_rules! compile_error {}
#[rustc_builtin_macro]
macro_rules! format_args { () => {} }
fn main() {
// Test a handful of built-in (eager) macros:
include!(invalid);
//^^^^^^^^^^^^^^^^^ could not convert tokens
include!("does not exist");
//^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist`
env!(invalid);
//^^^^^^^^^^^^^ could not convert tokens
env!("OUT_DIR");
//^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
compile_error!("compile_error works");
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works
// Lazy:
format_args!();
//^^^^^^^^^^^^^^ no rule matches input tokens
}
"#,
);
}
#[test]
fn macro_rules_diag() {
check_diagnostics(
r#"
macro_rules! m {
() => {};
}
fn f() {
m!();
m!(hi);
//^^^^^^ leftover tokens
}
"#,
);
}
#[test]
fn dollar_crate_in_builtin_macro() {
check_diagnostics(
r#"
#[macro_export]
#[rustc_builtin_macro]
macro_rules! format_args {}
#[macro_export]
macro_rules! arg { () => {} }
#[macro_export]
macro_rules! outer {
() => {
$crate::format_args!( "", $crate::arg!(1) )
};
}
fn f() {
outer!();
} //^^^^^^^^ leftover tokens
"#,
)
}
}

View File

@ -34,6 +34,18 @@ pub(super) fn unresolved_macro_call(
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn unresolved_macro_diag() {
check_diagnostics(
r#"
fn f() {
m!();
} //^ unresolved macro `m!`
"#,
);
}
#[test]
fn test_unresolved_macro_range() {
check_diagnostics(

View File

@ -0,0 +1,30 @@
use crate::{
diagnostics::{Diagnostic, DiagnosticsContext},
Severity,
};
// 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`).
pub(super) fn unresolved_proc_macro(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedProcMacro,
) -> Diagnostic {
// Use more accurate position if available.
let display_range = d
.precise_location
.unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
// FIXME: it would be nice to tell the user whether proc macros are currently disabled
let message = match &d.macro_name {
Some(name) => format!("proc macro `{}` not expanded", name),
None => "proc macro not expanded".to_string(),
};
Diagnostic::new("unresolved-proc-macro", message, display_range).severity(Severity::WeakWarning)
}