diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-10-23 23:05:25 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-10-23 23:05:25 +0100 |
commit | c483212f274e9a5e348451cd4bbd0487e172458b (patch) | |
tree | dcb279acf2cd6262ede408615a4b3f9bed8098fc /crates/hir_def/src | |
parent | ea25ae614b21237c4a536304da875bdc29f0c65a (diff) | |
parent | 5350c15e27bfb85d2e7ae3eae0e624197f2b9a70 (diff) |
Merge #6339
6339: Diagnose #[cfg]s in bodies r=matklad a=jonas-schievink
This PR threads diagnostics through body lowering using the `BodySourceMap`, and emits `InactiveCode` diagnostics for expressions, statements, and match arms that are `#[cfg]`d out.
Co-authored-by: Jonas Schievink <[email protected]>
Diffstat (limited to 'crates/hir_def/src')
-rw-r--r-- | crates/hir_def/src/body.rs | 63 | ||||
-rw-r--r-- | crates/hir_def/src/body/diagnostics.rs | 20 | ||||
-rw-r--r-- | crates/hir_def/src/body/lower.rs | 60 | ||||
-rw-r--r-- | crates/hir_def/src/body/tests.rs | 75 | ||||
-rw-r--r-- | crates/hir_def/src/diagnostics.rs | 13 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 34 | ||||
-rw-r--r-- | crates/hir_def/src/test_db.rs | 44 |
7 files changed, 214 insertions, 95 deletions
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. |
3 | mod lower; | 3 | mod lower; |
4 | mod diagnostics; | ||
5 | #[cfg(test)] | ||
6 | mod tests; | ||
4 | pub mod scope; | 7 | pub mod scope; |
5 | 8 | ||
6 | use std::{mem, ops::Index, sync::Arc}; | 9 | use std::{mem, ops::Index, sync::Arc}; |
@@ -10,7 +13,10 @@ use base_db::CrateId; | |||
10 | use cfg::CfgOptions; | 13 | use cfg::CfgOptions; |
11 | use drop_bomb::DropBomb; | 14 | use drop_bomb::DropBomb; |
12 | use either::Either; | 15 | 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 | }; | ||
14 | use rustc_hash::FxHashMap; | 20 | use rustc_hash::FxHashMap; |
15 | use syntax::{ast, AstNode, AstPtr}; | 21 | use syntax::{ast, AstNode, AstPtr}; |
16 | use test_utils::mark; | 22 | use 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)] | ||
324 | mod 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 | " | ||
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 | ); | ||
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 | |||
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 | } | ||
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 | }; |
21 | use test_utils::mark; | 21 | use 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 | ||
40 | use super::{ExprSource, PatSource}; | 41 | use super::{diagnostics::BodyDiagnostic, ExprSource, PatSource}; |
41 | 42 | ||
42 | pub(crate) struct LowerCtx { | 43 | pub(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 | ||
877 | impl From<ast::BinOp> for BinaryOp { | 909 | impl 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 @@ | |||
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 | } | ||
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index 532496b62..b221b290c 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs | |||
@@ -4,10 +4,17 @@ use std::any::Any; | |||
4 | use stdx::format_to; | 4 | use stdx::format_to; |
5 | 5 | ||
6 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; | 6 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; |
7 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; | 7 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; |
8 | use hir_expand::{HirFileId, InFile}; | 8 | use hir_expand::{HirFileId, InFile}; |
9 | use syntax::{ast, AstPtr, SyntaxNodePtr}; | 9 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
10 | 10 | ||
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 | |||
11 | // Diagnostic: unresolved-module | 18 | // Diagnostic: unresolved-module |
12 | // | 19 | // |
13 | // This diagnostic is triggered if rust-analyzer is unable to discover referred module. | 20 | // This diagnostic is triggered if rust-analyzer is unable to discover referred module. |
@@ -88,10 +95,10 @@ impl Diagnostic for UnresolvedImport { | |||
88 | } | 95 | } |
89 | } | 96 | } |
90 | 97 | ||
91 | // Diagnostic: unconfigured-code | 98 | // Diagnostic: inactive-code |
92 | // | 99 | // |
93 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. | 100 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. |
94 | #[derive(Debug)] | 101 | #[derive(Debug, Clone, Eq, PartialEq)] |
95 | pub struct InactiveCode { | 102 | pub struct InactiveCode { |
96 | pub file: HirFileId, | 103 | pub file: HirFileId, |
97 | pub node: SyntaxNodePtr, | 104 | pub node: SyntaxNodePtr, |
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs index 5972248de..1a7b98831 100644 --- a/crates/hir_def/src/nameres/tests/diagnostics.rs +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs | |||
@@ -1,42 +1,10 @@ | |||
1 | use base_db::fixture::WithFixture; | 1 | 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; | ||
8 | 2 | ||
9 | use crate::test_db::TestDB; | 3 | use crate::test_db::TestDB; |
10 | 4 | ||
11 | fn check_diagnostics(ra_fixture: &str) { | 5 | fn 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] |
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; | |||
12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; | 12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; |
13 | use rustc_hash::FxHashMap; | 13 | use rustc_hash::FxHashMap; |
14 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::TextRange; | 15 | use syntax::{TextRange, TextSize}; |
16 | use test_utils::extract_annotations; | 16 | use test_utils::extract_annotations; |
17 | 17 | ||
18 | use crate::db::DefDatabase; | 18 | use 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 | } |