Skip to content

Commit 93dc6f5

Browse files
author
Jonas Schievink
committed
Diagnose #[cfg]s in bodies
1 parent dd8a75b commit 93dc6f5

File tree

9 files changed

+218
-98
lines changed

9 files changed

+218
-98
lines changed

crates/hir/src/code_model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,7 @@ impl Function {
781781
}
782782

783783
pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
784+
hir_def::diagnostics::validate_body(db.upcast(), self.id.into(), sink);
784785
hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink);
785786
hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
786787
}

crates/hir_def/src/body.rs

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
//! Defines `Body`: a lowered representation of bodies of functions, statics and
22
//! consts.
33
mod lower;
4+
mod diagnostics;
5+
#[cfg(test)]
6+
mod tests;
47
pub mod scope;
58

69
use std::{mem, ops::Index, sync::Arc};
@@ -10,7 +13,10 @@ use base_db::CrateId;
1013
use cfg::CfgOptions;
1114
use drop_bomb::DropBomb;
1215
use either::Either;
13-
use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId};
16+
use hir_expand::{
17+
ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile,
18+
MacroDefId,
19+
};
1420
use rustc_hash::FxHashMap;
1521
use syntax::{ast, AstNode, AstPtr};
1622
use test_utils::mark;
@@ -150,8 +156,12 @@ impl Expander {
150156
InFile { file_id: self.current_file_id, value }
151157
}
152158

153-
pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool {
154-
self.cfg_expander.is_cfg_enabled(owner)
159+
pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
160+
self.cfg_expander.parse_attrs(owner)
161+
}
162+
163+
pub(crate) fn cfg_options(&self) -> &CfgOptions {
164+
&self.cfg_expander.cfg_options
155165
}
156166

157167
fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
@@ -219,6 +229,10 @@ pub struct BodySourceMap {
219229
pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
220230
field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
221231
expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
232+
233+
/// Diagnostics accumulated during body lowering. These contain `AstPtr`s and so are stored in
234+
/// the source map (since they're just as volatile).
235+
diagnostics: Vec<diagnostics::BodyDiagnostic>,
222236
}
223237

224238
#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
@@ -318,45 +332,10 @@ impl BodySourceMap {
318332
pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
319333
self.field_map[&(expr, field)].clone()
320334
}
321-
}
322-
323-
#[cfg(test)]
324-
mod tests {
325-
use base_db::{fixture::WithFixture, SourceDatabase};
326-
use test_utils::mark;
327335

328-
use crate::ModuleDefId;
329-
330-
use super::*;
331-
332-
fn lower(ra_fixture: &str) -> Arc<Body> {
333-
let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);
334-
335-
let krate = db.crate_graph().iter().next().unwrap();
336-
let def_map = db.crate_def_map(krate);
337-
let module = def_map.modules_for_file(file_id).next().unwrap();
338-
let module = &def_map[module];
339-
let fn_def = match module.scope.declarations().next().unwrap() {
340-
ModuleDefId::FunctionId(it) => it,
341-
_ => panic!(),
342-
};
343-
344-
db.body(fn_def.into())
345-
}
346-
347-
#[test]
348-
fn your_stack_belongs_to_me() {
349-
mark::check!(your_stack_belongs_to_me);
350-
lower(
351-
"
352-
macro_rules! n_nuple {
353-
($e:tt) => ();
354-
($($rest:tt)*) => {{
355-
(n_nuple!($($rest)*)None,)
356-
}};
357-
}
358-
fn main() { n_nuple!(1,2,3); }
359-
",
360-
);
336+
pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
337+
for diag in &self.diagnostics {
338+
diag.add_to(sink);
339+
}
361340
}
362341
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Diagnostics emitted during body lowering.
2+
3+
use hir_expand::diagnostics::DiagnosticSink;
4+
5+
use crate::diagnostics::InactiveCode;
6+
7+
#[derive(Debug, Eq, PartialEq)]
8+
pub enum BodyDiagnostic {
9+
InactiveCode(InactiveCode),
10+
}
11+
12+
impl BodyDiagnostic {
13+
pub fn add_to(&self, sink: &mut DiagnosticSink<'_>) {
14+
match self {
15+
BodyDiagnostic::InactiveCode(diag) => {
16+
sink.push(diag.clone());
17+
}
18+
}
19+
}
20+
}

crates/hir_def/src/body/lower.rs

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use syntax::{
1616
self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner,
1717
SlicePatComponents,
1818
},
19-
AstNode, AstPtr,
19+
AstNode, AstPtr, SyntaxNodePtr,
2020
};
2121
use test_utils::mark;
2222

@@ -25,6 +25,7 @@ use crate::{
2525
body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax},
2626
builtin_type::{BuiltinFloat, BuiltinInt},
2727
db::DefDatabase,
28+
diagnostics::InactiveCode,
2829
expr::{
2930
dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal,
3031
LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
@@ -37,7 +38,7 @@ use crate::{
3738
StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
3839
};
3940

40-
use super::{ExprSource, PatSource};
41+
use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
4142

4243
pub(crate) struct LowerCtx {
4344
hygiene: Hygiene,
@@ -176,7 +177,7 @@ impl ExprCollector<'_> {
176177

177178
fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
178179
let syntax_ptr = AstPtr::new(&expr);
179-
if !self.expander.is_cfg_enabled(&expr) {
180+
if self.check_cfg(&expr).is_none() {
180181
return self.missing_expr();
181182
}
182183

@@ -354,13 +355,15 @@ impl ExprCollector<'_> {
354355
let arms = if let Some(match_arm_list) = e.match_arm_list() {
355356
match_arm_list
356357
.arms()
357-
.map(|arm| MatchArm {
358-
pat: self.collect_pat_opt(arm.pat()),
359-
expr: self.collect_expr_opt(arm.expr()),
360-
guard: arm
361-
.guard()
362-
.and_then(|guard| guard.expr())
363-
.map(|e| self.collect_expr(e)),
358+
.filter_map(|arm| {
359+
self.check_cfg(&arm).map(|()| MatchArm {
360+
pat: self.collect_pat_opt(arm.pat()),
361+
expr: self.collect_expr_opt(arm.expr()),
362+
guard: arm
363+
.guard()
364+
.and_then(|guard| guard.expr())
365+
.map(|e| self.collect_expr(e)),
366+
})
364367
})
365368
.collect()
366369
} else {
@@ -406,9 +409,8 @@ impl ExprCollector<'_> {
406409
.fields()
407410
.inspect(|field| field_ptrs.push(AstPtr::new(field)))
408411
.filter_map(|field| {
409-
if !self.expander.is_cfg_enabled(&field) {
410-
return None;
411-
}
412+
self.check_cfg(&field)?;
413+
412414
let name = field.field_name()?.as_name();
413415

414416
Some(RecordLitField {
@@ -620,15 +622,23 @@ impl ExprCollector<'_> {
620622
.filter_map(|s| {
621623
let stmt = match s {
622624
ast::Stmt::LetStmt(stmt) => {
625+
self.check_cfg(&stmt)?;
626+
623627
let pat = self.collect_pat_opt(stmt.pat());
624628
let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
625629
let initializer = stmt.initializer().map(|e| self.collect_expr(e));
626630
Statement::Let { pat, type_ref, initializer }
627631
}
628632
ast::Stmt::ExprStmt(stmt) => {
633+
self.check_cfg(&stmt)?;
634+
629635
Statement::Expr(self.collect_expr_opt(stmt.expr()))
630636
}
631-
ast::Stmt::Item(_) => return None,
637+
ast::Stmt::Item(item) => {
638+
self.check_cfg(&item)?;
639+
640+
return None;
641+
}
632642
};
633643
Some(stmt)
634644
})
@@ -872,6 +882,28 @@ impl ExprCollector<'_> {
872882

873883
(args, ellipsis)
874884
}
885+
886+
/// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when
887+
/// not.
888+
fn check_cfg(&mut self, owner: &dyn ast::AttrsOwner) -> Option<()> {
889+
match self.expander.parse_attrs(owner).cfg() {
890+
Some(cfg) => {
891+
if self.expander.cfg_options().check(&cfg) != Some(false) {
892+
return Some(());
893+
}
894+
895+
self.source_map.diagnostics.push(BodyDiagnostic::InactiveCode(InactiveCode {
896+
file: self.expander.current_file_id,
897+
node: SyntaxNodePtr::new(owner.syntax()),
898+
cfg,
899+
opts: self.expander.cfg_options().clone(),
900+
}));
901+
902+
None
903+
}
904+
None => Some(()),
905+
}
906+
}
875907
}
876908

877909
impl From<ast::BinOp> for BinaryOp {

crates/hir_def/src/body/tests.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use base_db::{fixture::WithFixture, SourceDatabase};
2+
use test_utils::mark;
3+
4+
use crate::{test_db::TestDB, ModuleDefId};
5+
6+
use super::*;
7+
8+
fn lower(ra_fixture: &str) -> Arc<Body> {
9+
let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture);
10+
11+
let krate = db.crate_graph().iter().next().unwrap();
12+
let def_map = db.crate_def_map(krate);
13+
let module = def_map.modules_for_file(file_id).next().unwrap();
14+
let module = &def_map[module];
15+
let fn_def = match module.scope.declarations().next().unwrap() {
16+
ModuleDefId::FunctionId(it) => it,
17+
_ => panic!(),
18+
};
19+
20+
db.body(fn_def.into())
21+
}
22+
23+
fn check_diagnostics(ra_fixture: &str) {
24+
let db: TestDB = TestDB::with_files(ra_fixture);
25+
db.check_diagnostics();
26+
}
27+
28+
#[test]
29+
fn your_stack_belongs_to_me() {
30+
mark::check!(your_stack_belongs_to_me);
31+
lower(
32+
"
33+
macro_rules! n_nuple {
34+
($e:tt) => ();
35+
($($rest:tt)*) => {{
36+
(n_nuple!($($rest)*)None,)
37+
}};
38+
}
39+
fn main() { n_nuple!(1,2,3); }
40+
",
41+
);
42+
}
43+
44+
#[test]
45+
fn cfg_diagnostics() {
46+
check_diagnostics(
47+
r"
48+
fn f() {
49+
// The three g̶e̶n̶d̶e̶r̶s̶ statements:
50+
51+
#[cfg(a)] fn f() {} // Item statement
52+
//^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
53+
#[cfg(a)] {} // Expression statement
54+
//^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
55+
#[cfg(a)] let x = 0; // let statement
56+
//^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
57+
58+
abc(#[cfg(a)] 0);
59+
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
60+
let x = Struct {
61+
#[cfg(a)] f: 0,
62+
//^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
63+
};
64+
match () {
65+
() => (),
66+
#[cfg(a)] () => (),
67+
//^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
68+
}
69+
70+
#[cfg(a)] 0 // Trailing expression of block
71+
//^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
72+
}
73+
",
74+
);
75+
}

crates/hir_def/src/diagnostics.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ use std::any::Any;
44
use stdx::format_to;
55

66
use cfg::{CfgExpr, CfgOptions, DnfExpr};
7-
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode};
7+
use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
88
use hir_expand::{HirFileId, InFile};
99
use syntax::{ast, AstPtr, SyntaxNodePtr};
1010

11+
use crate::{db::DefDatabase, DefWithBodyId};
12+
13+
pub fn validate_body(db: &dyn DefDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
14+
let source_map = db.body_with_source_map(owner).1;
15+
source_map.add_diagnostics(db, sink);
16+
}
17+
1118
// Diagnostic: unresolved-module
1219
//
1320
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
@@ -91,7 +98,7 @@ impl Diagnostic for UnresolvedImport {
9198
// Diagnostic: unconfigured-code
9299
//
93100
// This diagnostic is shown for code with inactive `#[cfg]` attributes.
94-
#[derive(Debug)]
101+
#[derive(Debug, Clone, Eq, PartialEq)]
95102
pub struct InactiveCode {
96103
pub file: HirFileId,
97104
pub node: SyntaxNodePtr,

crates/hir_def/src/nameres/tests/diagnostics.rs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,10 @@
11
use base_db::fixture::WithFixture;
2-
use base_db::FileId;
3-
use base_db::SourceDatabaseExt;
4-
use hir_expand::db::AstDatabase;
5-
use rustc_hash::FxHashMap;
6-
use syntax::TextRange;
7-
use syntax::TextSize;
82

93
use crate::test_db::TestDB;
104

115
fn check_diagnostics(ra_fixture: &str) {
126
let db: TestDB = TestDB::with_files(ra_fixture);
13-
let annotations = db.extract_annotations();
14-
assert!(!annotations.is_empty());
15-
16-
let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
17-
db.diagnostics(|d| {
18-
let src = d.display_source();
19-
let root = db.parse_or_expand(src.file_id).unwrap();
20-
// FIXME: macros...
21-
let file_id = src.file_id.original_file(&db);
22-
let range = src.value.to_node(&root).text_range();
23-
let message = d.message().to_owned();
24-
actual.entry(file_id).or_default().push((range, message));
25-
});
26-
27-
for (file_id, diags) in actual.iter_mut() {
28-
diags.sort_by_key(|it| it.0.start());
29-
let text = db.file_text(*file_id);
30-
// For multiline spans, place them on line start
31-
for (range, content) in diags {
32-
if text[*range].contains('\n') {
33-
*range = TextRange::new(range.start(), range.start() + TextSize::from(1));
34-
*content = format!("... {}", content);
35-
}
36-
}
37-
}
38-
39-
assert_eq!(annotations, actual);
7+
db.check_diagnostics();
408
}
419

4210
#[test]

0 commit comments

Comments
 (0)