aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_def')
-rw-r--r--crates/hir_def/src/attr.rs14
-rw-r--r--crates/hir_def/src/body.rs63
-rw-r--r--crates/hir_def/src/body/diagnostics.rs20
-rw-r--r--crates/hir_def/src/body/lower.rs60
-rw-r--r--crates/hir_def/src/body/tests.rs75
-rw-r--r--crates/hir_def/src/diagnostics.rs28
-rw-r--r--crates/hir_def/src/nameres.rs16
-rw-r--r--crates/hir_def/src/nameres/collector.rs29
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs56
-rw-r--r--crates/hir_def/src/test_db.rs44
10 files changed, 288 insertions, 117 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index dea552a60..b2ce7ca3c 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -125,12 +125,20 @@ impl Attrs {
125 AttrQuery { attrs: self, key } 125 AttrQuery { attrs: self, key }
126 } 126 }
127 127
128 pub fn cfg(&self) -> impl Iterator<Item = CfgExpr> + '_ { 128 pub fn cfg(&self) -> Option<CfgExpr> {
129 // FIXME: handle cfg_attr :-) 129 // FIXME: handle cfg_attr :-)
130 self.by_key("cfg").tt_values().map(CfgExpr::parse) 130 let mut cfgs = self.by_key("cfg").tt_values().map(CfgExpr::parse).collect::<Vec<_>>();
131 match cfgs.len() {
132 0 => None,
133 1 => Some(cfgs.pop().unwrap()),
134 _ => Some(CfgExpr::All(cfgs)),
135 }
131 } 136 }
132 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool { 137 pub(crate) fn is_cfg_enabled(&self, cfg_options: &CfgOptions) -> bool {
133 self.cfg().all(|cfg| cfg_options.check(&cfg) != Some(false)) 138 match self.cfg() {
139 None => true,
140 Some(cfg) => cfg_options.check(&cfg) != Some(false),
141 }
134 } 142 }
135} 143}
136 144
diff --git a/crates/hir_def/src/body.rs b/crates/hir_def/src/body.rs
index d51036e4f..d10b1af01 100644
--- a/crates/hir_def/src/body.rs
+++ b/crates/hir_def/src/body.rs
@@ -1,6 +1,9 @@
1//! Defines `Body`: a lowered representation of bodies of functions, statics and 1//! Defines `Body`: a lowered representation of bodies of functions, statics and
2//! consts. 2//! consts.
3mod lower; 3mod lower;
4mod diagnostics;
5#[cfg(test)]
6mod tests;
4pub mod scope; 7pub mod scope;
5 8
6use std::{mem, ops::Index, sync::Arc}; 9use std::{mem, ops::Index, sync::Arc};
@@ -10,7 +13,10 @@ use base_db::CrateId;
10use cfg::CfgOptions; 13use cfg::CfgOptions;
11use drop_bomb::DropBomb; 14use drop_bomb::DropBomb;
12use either::Either; 15use either::Either;
13use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, AstId, HirFileId, InFile, MacroDefId}; 16use hir_expand::{
17 ast_id_map::AstIdMap, diagnostics::DiagnosticSink, hygiene::Hygiene, AstId, HirFileId, InFile,
18 MacroDefId,
19};
14use rustc_hash::FxHashMap; 20use rustc_hash::FxHashMap;
15use syntax::{ast, AstNode, AstPtr}; 21use syntax::{ast, AstNode, AstPtr};
16use test_utils::mark; 22use test_utils::mark;
@@ -150,8 +156,12 @@ impl Expander {
150 InFile { file_id: self.current_file_id, value } 156 InFile { file_id: self.current_file_id, value }
151 } 157 }
152 158
153 pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { 159 pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs {
154 self.cfg_expander.is_cfg_enabled(owner) 160 self.cfg_expander.parse_attrs(owner)
161 }
162
163 pub(crate) fn cfg_options(&self) -> &CfgOptions {
164 &self.cfg_expander.cfg_options
155 } 165 }
156 166
157 fn parse_path(&mut self, path: ast::Path) -> Option<Path> { 167 fn parse_path(&mut self, path: ast::Path) -> Option<Path> {
@@ -219,6 +229,10 @@ pub struct BodySourceMap {
219 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, 229 pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>,
220 field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, 230 field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>,
221 expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>, 231 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>,
222} 236}
223 237
224#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] 238#[derive(Default, Debug, Eq, PartialEq, Clone, Copy)]
@@ -318,45 +332,10 @@ impl BodySourceMap {
318 pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> { 332 pub fn field_syntax(&self, expr: ExprId, field: usize) -> InFile<AstPtr<ast::RecordExprField>> {
319 self.field_map[&(expr, field)].clone() 333 self.field_map[&(expr, field)].clone()
320 } 334 }
321}
322
323#[cfg(test)]
324mod tests {
325 use base_db::{fixture::WithFixture, SourceDatabase};
326 use test_utils::mark;
327 335
328 use crate::ModuleDefId; 336 pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) {
329 337 for diag in &self.diagnostics {
330 use super::*; 338 diag.add_to(sink);
331 339 }
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 "
352macro_rules! n_nuple {
353 ($e:tt) => ();
354 ($($rest:tt)*) => {{
355 (n_nuple!($($rest)*)None,)
356 }};
357}
358fn main() { n_nuple!(1,2,3); }
359",
360 );
361 } 340 }
362} 341}
diff --git a/crates/hir_def/src/body/diagnostics.rs b/crates/hir_def/src/body/diagnostics.rs
new file mode 100644
index 000000000..cfa47d189
--- /dev/null
+++ b/crates/hir_def/src/body/diagnostics.rs
@@ -0,0 +1,20 @@
1//! Diagnostics emitted during body lowering.
2
3use hir_expand::diagnostics::DiagnosticSink;
4
5use crate::diagnostics::InactiveCode;
6
7#[derive(Debug, Eq, PartialEq)]
8pub enum BodyDiagnostic {
9 InactiveCode(InactiveCode),
10}
11
12impl 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}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 01e72690a..ddc267b83 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -16,7 +16,7 @@ use syntax::{
16 self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner, 16 self, ArgListOwner, ArrayExprKind, AstChildren, LiteralKind, LoopBodyOwner, NameOwner,
17 SlicePatComponents, 17 SlicePatComponents,
18 }, 18 },
19 AstNode, AstPtr, 19 AstNode, AstPtr, SyntaxNodePtr,
20}; 20};
21use test_utils::mark; 21use test_utils::mark;
22 22
@@ -25,6 +25,7 @@ use crate::{
25 body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax}, 25 body::{Body, BodySourceMap, Expander, PatPtr, SyntheticSyntax},
26 builtin_type::{BuiltinFloat, BuiltinInt}, 26 builtin_type::{BuiltinFloat, BuiltinInt},
27 db::DefDatabase, 27 db::DefDatabase,
28 diagnostics::InactiveCode,
28 expr::{ 29 expr::{
29 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, 30 dummy_expr_id, ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal,
30 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, 31 LogicOp, MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement,
@@ -37,7 +38,7 @@ use crate::{
37 StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc, 38 StaticLoc, StructLoc, TraitLoc, TypeAliasLoc, UnionLoc,
38}; 39};
39 40
40use super::{ExprSource, PatSource}; 41use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource};
41 42
42pub(crate) struct LowerCtx { 43pub(crate) struct LowerCtx {
43 hygiene: Hygiene, 44 hygiene: Hygiene,
@@ -176,7 +177,7 @@ impl ExprCollector<'_> {
176 177
177 fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { 178 fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
178 let syntax_ptr = AstPtr::new(&expr); 179 let syntax_ptr = AstPtr::new(&expr);
179 if !self.expander.is_cfg_enabled(&expr) { 180 if self.check_cfg(&expr).is_none() {
180 return self.missing_expr(); 181 return self.missing_expr();
181 } 182 }
182 183
@@ -354,13 +355,15 @@ impl ExprCollector<'_> {
354 let arms = if let Some(match_arm_list) = e.match_arm_list() { 355 let arms = if let Some(match_arm_list) = e.match_arm_list() {
355 match_arm_list 356 match_arm_list
356 .arms() 357 .arms()
357 .map(|arm| MatchArm { 358 .filter_map(|arm| {
358 pat: self.collect_pat_opt(arm.pat()), 359 self.check_cfg(&arm).map(|()| MatchArm {
359 expr: self.collect_expr_opt(arm.expr()), 360 pat: self.collect_pat_opt(arm.pat()),
360 guard: arm 361 expr: self.collect_expr_opt(arm.expr()),
361 .guard() 362 guard: arm
362 .and_then(|guard| guard.expr()) 363 .guard()
363 .map(|e| self.collect_expr(e)), 364 .and_then(|guard| guard.expr())
365 .map(|e| self.collect_expr(e)),
366 })
364 }) 367 })
365 .collect() 368 .collect()
366 } else { 369 } else {
@@ -406,9 +409,8 @@ impl ExprCollector<'_> {
406 .fields() 409 .fields()
407 .inspect(|field| field_ptrs.push(AstPtr::new(field))) 410 .inspect(|field| field_ptrs.push(AstPtr::new(field)))
408 .filter_map(|field| { 411 .filter_map(|field| {
409 if !self.expander.is_cfg_enabled(&field) { 412 self.check_cfg(&field)?;
410 return None; 413
411 }
412 let name = field.field_name()?.as_name(); 414 let name = field.field_name()?.as_name();
413 415
414 Some(RecordLitField { 416 Some(RecordLitField {
@@ -620,15 +622,23 @@ impl ExprCollector<'_> {
620 .filter_map(|s| { 622 .filter_map(|s| {
621 let stmt = match s { 623 let stmt = match s {
622 ast::Stmt::LetStmt(stmt) => { 624 ast::Stmt::LetStmt(stmt) => {
625 self.check_cfg(&stmt)?;
626
623 let pat = self.collect_pat_opt(stmt.pat()); 627 let pat = self.collect_pat_opt(stmt.pat());
624 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it)); 628 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
625 let initializer = stmt.initializer().map(|e| self.collect_expr(e)); 629 let initializer = stmt.initializer().map(|e| self.collect_expr(e));
626 Statement::Let { pat, type_ref, initializer } 630 Statement::Let { pat, type_ref, initializer }
627 } 631 }
628 ast::Stmt::ExprStmt(stmt) => { 632 ast::Stmt::ExprStmt(stmt) => {
633 self.check_cfg(&stmt)?;
634
629 Statement::Expr(self.collect_expr_opt(stmt.expr())) 635 Statement::Expr(self.collect_expr_opt(stmt.expr()))
630 } 636 }
631 ast::Stmt::Item(_) => return None, 637 ast::Stmt::Item(item) => {
638 self.check_cfg(&item)?;
639
640 return None;
641 }
632 }; 642 };
633 Some(stmt) 643 Some(stmt)
634 }) 644 })
@@ -872,6 +882,28 @@ impl ExprCollector<'_> {
872 882
873 (args, ellipsis) 883 (args, ellipsis)
874 } 884 }
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 }
875} 907}
876 908
877impl From<ast::BinOp> for BinaryOp { 909impl From<ast::BinOp> for BinaryOp {
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
new file mode 100644
index 000000000..f07df5cee
--- /dev/null
+++ b/crates/hir_def/src/body/tests.rs
@@ -0,0 +1,75 @@
1use base_db::{fixture::WithFixture, SourceDatabase};
2use test_utils::mark;
3
4use crate::{test_db::TestDB, ModuleDefId};
5
6use super::*;
7
8fn 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
23fn check_diagnostics(ra_fixture: &str) {
24 let db: TestDB = TestDB::with_files(ra_fixture);
25 db.check_diagnostics();
26}
27
28#[test]
29fn your_stack_belongs_to_me() {
30 mark::check!(your_stack_belongs_to_me);
31 lower(
32 "
33macro_rules! n_nuple {
34 ($e:tt) => ();
35 ($($rest:tt)*) => {{
36 (n_nuple!($($rest)*)None,)
37 }};
38}
39fn main() { n_nuple!(1,2,3); }
40",
41 );
42}
43
44#[test]
45fn cfg_diagnostics() {
46 check_diagnostics(
47 r"
48fn 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}
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index c9c08b01f..b221b290c 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -1,11 +1,19 @@
1//! Diagnostics produced by `hir_def`. 1//! Diagnostics produced by `hir_def`.
2 2
3use std::any::Any; 3use std::any::Any;
4use stdx::format_to;
4 5
5use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; 6use cfg::{CfgExpr, CfgOptions, DnfExpr};
7use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
8use hir_expand::{HirFileId, InFile};
6use syntax::{ast, AstPtr, SyntaxNodePtr}; 9use syntax::{ast, AstPtr, SyntaxNodePtr};
7 10
8use hir_expand::{HirFileId, InFile}; 11use crate::{db::DefDatabase, DefWithBodyId};
12
13pub 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}
9 17
10// Diagnostic: unresolved-module 18// Diagnostic: unresolved-module
11// 19//
@@ -87,13 +95,15 @@ impl Diagnostic for UnresolvedImport {
87 } 95 }
88} 96}
89 97
90// Diagnostic: unconfigured-code 98// Diagnostic: inactive-code
91// 99//
92// This diagnostic is shown for code with inactive `#[cfg]` attributes. 100// This diagnostic is shown for code with inactive `#[cfg]` attributes.
93#[derive(Debug)] 101#[derive(Debug, Clone, Eq, PartialEq)]
94pub struct InactiveCode { 102pub struct InactiveCode {
95 pub file: HirFileId, 103 pub file: HirFileId,
96 pub node: SyntaxNodePtr, 104 pub node: SyntaxNodePtr,
105 pub cfg: CfgExpr,
106 pub opts: CfgOptions,
97} 107}
98 108
99impl Diagnostic for InactiveCode { 109impl Diagnostic for InactiveCode {
@@ -101,8 +111,14 @@ impl Diagnostic for InactiveCode {
101 DiagnosticCode("inactive-code") 111 DiagnosticCode("inactive-code")
102 } 112 }
103 fn message(&self) -> String { 113 fn message(&self) -> String {
104 // FIXME: say *why* it is configured out 114 let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
105 "code is inactive due to #[cfg] directives".to_string() 115 let mut buf = "code is inactive due to #[cfg] directives".to_string();
116
117 if let Some(inactive) = inactive {
118 format_to!(buf, ": {}", inactive);
119 }
120
121 buf
106 } 122 }
107 fn display_source(&self) -> InFile<SyntaxNodePtr> { 123 fn display_source(&self) -> InFile<SyntaxNodePtr> {
108 InFile::new(self.file, self.node.clone()) 124 InFile::new(self.file, self.node.clone())
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 01a28aeeb..eb41d324e 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -283,6 +283,7 @@ pub enum ModuleSource {
283} 283}
284 284
285mod diagnostics { 285mod diagnostics {
286 use cfg::{CfgExpr, CfgOptions};
286 use hir_expand::diagnostics::DiagnosticSink; 287 use hir_expand::diagnostics::DiagnosticSink;
287 use hir_expand::hygiene::Hygiene; 288 use hir_expand::hygiene::Hygiene;
288 use hir_expand::InFile; 289 use hir_expand::InFile;
@@ -299,7 +300,7 @@ mod diagnostics {
299 300
300 UnresolvedImport { ast: AstId<ast::Use>, index: usize }, 301 UnresolvedImport { ast: AstId<ast::Use>, index: usize },
301 302
302 UnconfiguredCode { ast: InFile<SyntaxNodePtr> }, 303 UnconfiguredCode { ast: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions },
303 } 304 }
304 305
305 #[derive(Debug, PartialEq, Eq)] 306 #[derive(Debug, PartialEq, Eq)]
@@ -341,8 +342,10 @@ mod diagnostics {
341 pub(super) fn unconfigured_code( 342 pub(super) fn unconfigured_code(
342 container: LocalModuleId, 343 container: LocalModuleId,
343 ast: InFile<SyntaxNodePtr>, 344 ast: InFile<SyntaxNodePtr>,
345 cfg: CfgExpr,
346 opts: CfgOptions,
344 ) -> Self { 347 ) -> Self {
345 Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast } } 348 Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } }
346 } 349 }
347 350
348 pub(super) fn add_to( 351 pub(super) fn add_to(
@@ -395,8 +398,13 @@ mod diagnostics {
395 } 398 }
396 } 399 }
397 400
398 DiagnosticKind::UnconfiguredCode { ast } => { 401 DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
399 sink.push(InactiveCode { file: ast.file_id, node: ast.value.clone() }); 402 sink.push(InactiveCode {
403 file: ast.file_id,
404 node: ast.value.clone(),
405 cfg: cfg.clone(),
406 opts: opts.clone(),
407 });
400 } 408 }
401 } 409 }
402 } 410 }
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index bff8edb62..f30172d90 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -6,7 +6,7 @@
6use std::iter; 6use std::iter;
7 7
8use base_db::{CrateId, FileId, ProcMacroId}; 8use base_db::{CrateId, FileId, ProcMacroId};
9use cfg::CfgOptions; 9use cfg::{CfgExpr, CfgOptions};
10use hir_expand::InFile; 10use hir_expand::InFile;
11use hir_expand::{ 11use hir_expand::{
12 ast_id_map::FileAstId, 12 ast_id_map::FileAstId,
@@ -900,7 +900,8 @@ impl ModCollector<'_, '_> {
900 // `#[macro_use] extern crate` is hoisted to imports macros before collecting 900 // `#[macro_use] extern crate` is hoisted to imports macros before collecting
901 // any other items. 901 // any other items.
902 for item in items { 902 for item in items {
903 if self.is_cfg_enabled(self.item_tree.attrs((*item).into())) { 903 let attrs = self.item_tree.attrs((*item).into());
904 if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) {
904 if let ModItem::ExternCrate(id) = item { 905 if let ModItem::ExternCrate(id) = item {
905 let import = self.item_tree[*id].clone(); 906 let import = self.item_tree[*id].clone();
906 if import.is_macro_use { 907 if import.is_macro_use {
@@ -912,9 +913,11 @@ impl ModCollector<'_, '_> {
912 913
913 for &item in items { 914 for &item in items {
914 let attrs = self.item_tree.attrs(item.into()); 915 let attrs = self.item_tree.attrs(item.into());
915 if !self.is_cfg_enabled(attrs) { 916 if let Some(cfg) = attrs.cfg() {
916 self.emit_unconfigured_diagnostic(item); 917 if !self.is_cfg_enabled(&cfg) {
917 continue; 918 self.emit_unconfigured_diagnostic(item, &cfg);
919 continue;
920 }
918 } 921 }
919 let module = 922 let module =
920 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; 923 ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id };
@@ -1321,20 +1324,22 @@ impl ModCollector<'_, '_> {
1321 } 1324 }
1322 } 1325 }
1323 1326
1324 fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { 1327 fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool {
1325 attrs.is_cfg_enabled(self.def_collector.cfg_options) 1328 self.def_collector.cfg_options.check(cfg) != Some(false)
1326 } 1329 }
1327 1330
1328 fn emit_unconfigured_diagnostic(&mut self, item: ModItem) { 1331 fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) {
1329 let ast_id = item.ast_id(self.item_tree); 1332 let ast_id = item.ast_id(self.item_tree);
1330 let id_map = self.def_collector.db.ast_id_map(self.file_id); 1333 let id_map = self.def_collector.db.ast_id_map(self.file_id);
1331 let syntax_ptr = id_map.get(ast_id).syntax_node_ptr(); 1334 let syntax_ptr = id_map.get(ast_id).syntax_node_ptr();
1332 1335
1333 let ast_node = InFile::new(self.file_id, syntax_ptr); 1336 let ast_node = InFile::new(self.file_id, syntax_ptr);
1334 self.def_collector 1337 self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code(
1335 .def_map 1338 self.module_id,
1336 .diagnostics 1339 ast_node,
1337 .push(DefDiagnostic::unconfigured_code(self.module_id, ast_node)); 1340 cfg.clone(),
1341 self.def_collector.cfg_options.clone(),
1342 ));
1338 } 1343 }
1339} 1344}
1340 1345
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 576b813d2..1a7b98831 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -1,42 +1,10 @@
1use base_db::fixture::WithFixture; 1use base_db::fixture::WithFixture;
2use base_db::FileId;
3use base_db::SourceDatabaseExt;
4use hir_expand::db::AstDatabase;
5use rustc_hash::FxHashMap;
6use syntax::TextRange;
7use syntax::TextSize;
8 2
9use crate::test_db::TestDB; 3use crate::test_db::TestDB;
10 4
11fn check_diagnostics(ra_fixture: &str) { 5fn check_diagnostics(ra_fixture: &str) {
12 let db: TestDB = TestDB::with_files(ra_fixture); 6 let db: TestDB = TestDB::with_files(ra_fixture);
13 let annotations = db.extract_annotations(); 7 db.check_diagnostics();
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);
40} 8}
41 9
42#[test] 10#[test]
@@ -129,3 +97,25 @@ fn unresolved_module() {
129 ", 97 ",
130 ); 98 );
131} 99}
100
101#[test]
102fn inactive_item() {
103 // Additional tests in `cfg` crate. This only tests disabled cfgs.
104
105 check_diagnostics(
106 r#"
107 //- /lib.rs
108 #[cfg(no)] pub fn f() {}
109 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
110
111 #[cfg(no)] #[cfg(no2)] mod m;
112 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
113
114 #[cfg(all(not(a), b))] enum E {}
115 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
116
117 #[cfg(feature = "std")] use std;
118 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
119 "#,
120 );
121}
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index fb1d3c974..2b36c824a 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -12,10 +12,10 @@ use hir_expand::diagnostics::Diagnostic;
12use hir_expand::diagnostics::DiagnosticSinkBuilder; 12use hir_expand::diagnostics::DiagnosticSinkBuilder;
13use rustc_hash::FxHashMap; 13use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15use syntax::TextRange; 15use syntax::{TextRange, TextSize};
16use test_utils::extract_annotations; 16use test_utils::extract_annotations;
17 17
18use crate::db::DefDatabase; 18use crate::{db::DefDatabase, ModuleDefId};
19 19
20#[salsa::database( 20#[salsa::database(
21 base_db::SourceDatabaseExtStorage, 21 base_db::SourceDatabaseExtStorage,
@@ -135,9 +135,47 @@ impl TestDB {
135 let crate_def_map = self.crate_def_map(krate); 135 let crate_def_map = self.crate_def_map(krate);
136 136
137 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); 137 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
138 for (module_id, _) in crate_def_map.modules.iter() { 138 for (module_id, module) in crate_def_map.modules.iter() {
139 crate_def_map.add_diagnostics(self, module_id, &mut sink); 139 crate_def_map.add_diagnostics(self, module_id, &mut sink);
140
141 for decl in module.scope.declarations() {
142 if let ModuleDefId::FunctionId(it) = decl {
143 let source_map = self.body_with_source_map(it.into()).1;
144 source_map.add_diagnostics(self, &mut sink);
145 }
146 }
140 } 147 }
141 } 148 }
142 } 149 }
150
151 pub fn check_diagnostics(&self) {
152 let db: &TestDB = self;
153 let annotations = db.extract_annotations();
154 assert!(!annotations.is_empty());
155
156 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
157 db.diagnostics(|d| {
158 let src = d.display_source();
159 let root = db.parse_or_expand(src.file_id).unwrap();
160 // FIXME: macros...
161 let file_id = src.file_id.original_file(db);
162 let range = src.value.to_node(&root).text_range();
163 let message = d.message().to_owned();
164 actual.entry(file_id).or_default().push((range, message));
165 });
166
167 for (file_id, diags) in actual.iter_mut() {
168 diags.sort_by_key(|it| it.0.start());
169 let text = db.file_text(*file_id);
170 // For multiline spans, place them on line start
171 for (range, content) in diags {
172 if text[*range].contains('\n') {
173 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
174 *content = format!("... {}", content);
175 }
176 }
177 }
178
179 assert_eq!(annotations, actual);
180 }
143} 181}