diff options
Diffstat (limited to 'crates/hir_def/src')
-rw-r--r-- | crates/hir_def/src/attr.rs | 14 | ||||
-rw-r--r-- | crates/hir_def/src/body.rs | 69 | ||||
-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 | 54 | ||||
-rw-r--r-- | crates/hir_def/src/import_map.rs | 6 | ||||
-rw-r--r-- | crates/hir_def/src/item_tree.rs | 18 | ||||
-rw-r--r-- | crates/hir_def/src/item_tree/lower.rs | 47 | ||||
-rw-r--r-- | crates/hir_def/src/nameres.rs | 29 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 30 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 56 | ||||
-rw-r--r-- | crates/hir_def/src/test_db.rs | 44 |
13 files changed, 406 insertions, 116 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 9a9a605dd..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; |
@@ -105,14 +111,16 @@ impl Expander { | |||
105 | 111 | ||
106 | let macro_call = InFile::new(self.current_file_id, ¯o_call); | 112 | let macro_call = InFile::new(self.current_file_id, ¯o_call); |
107 | 113 | ||
108 | if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, |path| { | 114 | let resolver = |path: ModPath| -> Option<MacroDefId> { |
109 | if let Some(local_scope) = local_scope { | 115 | if let Some(local_scope) = local_scope { |
110 | if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) { | 116 | if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) { |
111 | return Some(def); | 117 | return Some(def); |
112 | } | 118 | } |
113 | } | 119 | } |
114 | self.resolve_path_as_macro(db, &path) | 120 | self.resolve_path_as_macro(db, &path) |
115 | }) { | 121 | }; |
122 | |||
123 | if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, resolver) { | ||
116 | let file_id = call_id.as_file(); | 124 | let file_id = call_id.as_file(); |
117 | if let Some(node) = db.parse_or_expand(file_id) { | 125 | if let Some(node) = db.parse_or_expand(file_id) { |
118 | if let Some(expr) = T::cast(node) { | 126 | if let Some(expr) = T::cast(node) { |
@@ -148,8 +156,12 @@ impl Expander { | |||
148 | InFile { file_id: self.current_file_id, value } | 156 | InFile { file_id: self.current_file_id, value } |
149 | } | 157 | } |
150 | 158 | ||
151 | pub(crate) fn is_cfg_enabled(&self, owner: &dyn ast::AttrsOwner) -> bool { | 159 | pub(crate) fn parse_attrs(&self, owner: &dyn ast::AttrsOwner) -> Attrs { |
152 | 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 | ||
153 | } | 165 | } |
154 | 166 | ||
155 | fn parse_path(&mut self, path: ast::Path) -> Option<Path> { | 167 | fn parse_path(&mut self, path: ast::Path) -> Option<Path> { |
@@ -217,6 +229,10 @@ pub struct BodySourceMap { | |||
217 | pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, | 229 | pat_map_back: ArenaMap<PatId, Result<PatSource, SyntheticSyntax>>, |
218 | field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, | 230 | field_map: FxHashMap<(ExprId, usize), InFile<AstPtr<ast::RecordExprField>>>, |
219 | 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>, | ||
220 | } | 236 | } |
221 | 237 | ||
222 | #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] | 238 | #[derive(Default, Debug, Eq, PartialEq, Clone, Copy)] |
@@ -316,45 +332,10 @@ impl BodySourceMap { | |||
316 | 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>> { |
317 | self.field_map[&(expr, field)].clone() | 333 | self.field_map[&(expr, field)].clone() |
318 | } | 334 | } |
319 | } | ||
320 | 335 | ||
321 | #[cfg(test)] | 336 | pub(crate) fn add_diagnostics(&self, _db: &dyn DefDatabase, sink: &mut DiagnosticSink<'_>) { |
322 | mod tests { | 337 | for diag in &self.diagnostics { |
323 | use base_db::{fixture::WithFixture, SourceDatabase}; | 338 | diag.add_to(sink); |
324 | use test_utils::mark; | 339 | } |
325 | |||
326 | use crate::ModuleDefId; | ||
327 | |||
328 | use super::*; | ||
329 | |||
330 | fn lower(ra_fixture: &str) -> Arc<Body> { | ||
331 | let (db, file_id) = crate::test_db::TestDB::with_single_file(ra_fixture); | ||
332 | |||
333 | let krate = db.crate_graph().iter().next().unwrap(); | ||
334 | let def_map = db.crate_def_map(krate); | ||
335 | let module = def_map.modules_for_file(file_id).next().unwrap(); | ||
336 | let module = &def_map[module]; | ||
337 | let fn_def = match module.scope.declarations().next().unwrap() { | ||
338 | ModuleDefId::FunctionId(it) => it, | ||
339 | _ => panic!(), | ||
340 | }; | ||
341 | |||
342 | db.body(fn_def.into()) | ||
343 | } | ||
344 | |||
345 | #[test] | ||
346 | fn your_stack_belongs_to_me() { | ||
347 | mark::check!(your_stack_belongs_to_me); | ||
348 | lower( | ||
349 | " | ||
350 | macro_rules! n_nuple { | ||
351 | ($e:tt) => (); | ||
352 | ($($rest:tt)*) => {{ | ||
353 | (n_nuple!($($rest)*)None,) | ||
354 | }}; | ||
355 | } | ||
356 | fn main() { n_nuple!(1,2,3); } | ||
357 | ", | ||
358 | ); | ||
359 | } | 340 | } |
360 | } | 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 001b3c5db..b221b290c 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs | |||
@@ -1,12 +1,23 @@ | |||
1 | //! Diagnostics produced by `hir_def`. | 1 | //! Diagnostics produced by `hir_def`. |
2 | 2 | ||
3 | use std::any::Any; | 3 | use std::any::Any; |
4 | use stdx::format_to; | ||
4 | 5 | ||
5 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode}; | 6 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; |
7 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; | ||
8 | use hir_expand::{HirFileId, InFile}; | ||
6 | use syntax::{ast, AstPtr, SyntaxNodePtr}; | 9 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
7 | 10 | ||
8 | use hir_expand::{HirFileId, InFile}; | 11 | use crate::{db::DefDatabase, DefWithBodyId}; |
9 | 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 | |||
18 | // Diagnostic: unresolved-module | ||
19 | // | ||
20 | // This diagnostic is triggered if rust-analyzer is unable to discover referred module. | ||
10 | #[derive(Debug)] | 21 | #[derive(Debug)] |
11 | pub struct UnresolvedModule { | 22 | pub struct UnresolvedModule { |
12 | pub file: HirFileId, | 23 | pub file: HirFileId, |
@@ -29,6 +40,9 @@ impl Diagnostic for UnresolvedModule { | |||
29 | } | 40 | } |
30 | } | 41 | } |
31 | 42 | ||
43 | // Diagnostic: unresolved-extern-crate | ||
44 | // | ||
45 | // This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate. | ||
32 | #[derive(Debug)] | 46 | #[derive(Debug)] |
33 | pub struct UnresolvedExternCrate { | 47 | pub struct UnresolvedExternCrate { |
34 | pub file: HirFileId, | 48 | pub file: HirFileId, |
@@ -50,6 +64,9 @@ impl Diagnostic for UnresolvedExternCrate { | |||
50 | } | 64 | } |
51 | } | 65 | } |
52 | 66 | ||
67 | // Diagnostic: unresolved-import | ||
68 | // | ||
69 | // This diagnostic is triggered if rust-analyzer is unable to discover imported module. | ||
53 | #[derive(Debug)] | 70 | #[derive(Debug)] |
54 | pub struct UnresolvedImport { | 71 | pub struct UnresolvedImport { |
55 | pub file: HirFileId, | 72 | pub file: HirFileId, |
@@ -77,3 +94,36 @@ impl Diagnostic for UnresolvedImport { | |||
77 | true | 94 | true |
78 | } | 95 | } |
79 | } | 96 | } |
97 | |||
98 | // Diagnostic: inactive-code | ||
99 | // | ||
100 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. | ||
101 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
102 | pub struct InactiveCode { | ||
103 | pub file: HirFileId, | ||
104 | pub node: SyntaxNodePtr, | ||
105 | pub cfg: CfgExpr, | ||
106 | pub opts: CfgOptions, | ||
107 | } | ||
108 | |||
109 | impl Diagnostic for InactiveCode { | ||
110 | fn code(&self) -> DiagnosticCode { | ||
111 | DiagnosticCode("inactive-code") | ||
112 | } | ||
113 | fn message(&self) -> String { | ||
114 | let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts); | ||
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 | ||
122 | } | ||
123 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
124 | InFile::new(self.file, self.node.clone()) | ||
125 | } | ||
126 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
127 | self | ||
128 | } | ||
129 | } | ||
diff --git a/crates/hir_def/src/import_map.rs b/crates/hir_def/src/import_map.rs index 028cae2e7..1e24f29a8 100644 --- a/crates/hir_def/src/import_map.rs +++ b/crates/hir_def/src/import_map.rs | |||
@@ -356,7 +356,7 @@ mod tests { | |||
356 | let krate = crate_graph | 356 | let krate = crate_graph |
357 | .iter() | 357 | .iter() |
358 | .find(|krate| { | 358 | .find(|krate| { |
359 | crate_graph[*krate].declaration_name.as_ref().map(|n| n.to_string()) | 359 | crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) |
360 | == Some(crate_name.to_string()) | 360 | == Some(crate_name.to_string()) |
361 | }) | 361 | }) |
362 | .unwrap(); | 362 | .unwrap(); |
@@ -375,7 +375,7 @@ mod tests { | |||
375 | let path = map.path_of(item).unwrap(); | 375 | let path = map.path_of(item).unwrap(); |
376 | format!( | 376 | format!( |
377 | "{}::{} ({})\n", | 377 | "{}::{} ({})\n", |
378 | crate_graph[krate].declaration_name.as_ref().unwrap(), | 378 | crate_graph[krate].display_name.as_ref().unwrap(), |
379 | path, | 379 | path, |
380 | mark | 380 | mark |
381 | ) | 381 | ) |
@@ -416,7 +416,7 @@ mod tests { | |||
416 | .iter() | 416 | .iter() |
417 | .filter_map(|krate| { | 417 | .filter_map(|krate| { |
418 | let cdata = &crate_graph[krate]; | 418 | let cdata = &crate_graph[krate]; |
419 | let name = cdata.declaration_name.as_ref()?; | 419 | let name = cdata.display_name.as_ref()?; |
420 | 420 | ||
421 | let map = db.import_map(krate); | 421 | let map = db.import_map(krate); |
422 | 422 | ||
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 8a1121bbd..7eb388bae 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs | |||
@@ -672,6 +672,24 @@ impl ModItem { | |||
672 | pub fn downcast<N: ItemTreeNode>(self) -> Option<FileItemTreeId<N>> { | 672 | pub fn downcast<N: ItemTreeNode>(self) -> Option<FileItemTreeId<N>> { |
673 | N::id_from_mod_item(self) | 673 | N::id_from_mod_item(self) |
674 | } | 674 | } |
675 | |||
676 | pub fn ast_id(&self, tree: &ItemTree) -> FileAstId<ast::Item> { | ||
677 | match self { | ||
678 | ModItem::Import(it) => tree[it.index].ast_id().upcast(), | ||
679 | ModItem::ExternCrate(it) => tree[it.index].ast_id().upcast(), | ||
680 | ModItem::Function(it) => tree[it.index].ast_id().upcast(), | ||
681 | ModItem::Struct(it) => tree[it.index].ast_id().upcast(), | ||
682 | ModItem::Union(it) => tree[it.index].ast_id().upcast(), | ||
683 | ModItem::Enum(it) => tree[it.index].ast_id().upcast(), | ||
684 | ModItem::Const(it) => tree[it.index].ast_id().upcast(), | ||
685 | ModItem::Static(it) => tree[it.index].ast_id().upcast(), | ||
686 | ModItem::Trait(it) => tree[it.index].ast_id().upcast(), | ||
687 | ModItem::Impl(it) => tree[it.index].ast_id().upcast(), | ||
688 | ModItem::TypeAlias(it) => tree[it.index].ast_id().upcast(), | ||
689 | ModItem::Mod(it) => tree[it.index].ast_id().upcast(), | ||
690 | ModItem::MacroCall(it) => tree[it.index].ast_id().upcast(), | ||
691 | } | ||
692 | } | ||
675 | } | 693 | } |
676 | 694 | ||
677 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | 695 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] |
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index 3328639cf..ca7fb4a43 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use std::{collections::hash_map::Entry, mem, sync::Arc}; | 3 | use std::{collections::hash_map::Entry, mem, sync::Arc}; |
4 | 4 | ||
5 | use arena::map::ArenaMap; | 5 | use arena::map::ArenaMap; |
6 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, HirFileId}; | 6 | use hir_expand::{ast_id_map::AstIdMap, hygiene::Hygiene, name::known, HirFileId}; |
7 | use smallvec::SmallVec; | 7 | use smallvec::SmallVec; |
8 | use syntax::{ | 8 | use syntax::{ |
9 | ast::{self, ModuleItemOwner}, | 9 | ast::{self, ModuleItemOwner}, |
@@ -555,7 +555,8 @@ impl Ctx { | |||
555 | let id: ModItem = match item { | 555 | let id: ModItem = match item { |
556 | ast::ExternItem::Fn(ast) => { | 556 | ast::ExternItem::Fn(ast) => { |
557 | let func = self.lower_function(&ast)?; | 557 | let func = self.lower_function(&ast)?; |
558 | self.data().functions[func.index].is_unsafe = true; | 558 | self.data().functions[func.index].is_unsafe = |
559 | is_intrinsic_fn_unsafe(&self.data().functions[func.index].name); | ||
559 | func.into() | 560 | func.into() |
560 | } | 561 | } |
561 | ast::ExternItem::Static(ast) => { | 562 | ast::ExternItem::Static(ast) => { |
@@ -713,3 +714,45 @@ enum GenericsOwner<'a> { | |||
713 | TypeAlias, | 714 | TypeAlias, |
714 | Impl, | 715 | Impl, |
715 | } | 716 | } |
717 | |||
718 | /// Returns `true` if the given intrinsic is unsafe to call, or false otherwise. | ||
719 | fn is_intrinsic_fn_unsafe(name: &Name) -> bool { | ||
720 | // Should be kept in sync with https://github.com/rust-lang/rust/blob/c6e4db620a7d2f569f11dcab627430921ea8aacf/compiler/rustc_typeck/src/check/intrinsic.rs#L68 | ||
721 | ![ | ||
722 | known::abort, | ||
723 | known::min_align_of, | ||
724 | known::needs_drop, | ||
725 | known::caller_location, | ||
726 | known::size_of_val, | ||
727 | known::min_align_of_val, | ||
728 | known::add_with_overflow, | ||
729 | known::sub_with_overflow, | ||
730 | known::mul_with_overflow, | ||
731 | known::wrapping_add, | ||
732 | known::wrapping_sub, | ||
733 | known::wrapping_mul, | ||
734 | known::saturating_add, | ||
735 | known::saturating_sub, | ||
736 | known::rotate_left, | ||
737 | known::rotate_right, | ||
738 | known::ctpop, | ||
739 | known::ctlz, | ||
740 | known::cttz, | ||
741 | known::bswap, | ||
742 | known::bitreverse, | ||
743 | known::discriminant_value, | ||
744 | known::type_id, | ||
745 | known::likely, | ||
746 | known::unlikely, | ||
747 | known::ptr_guaranteed_eq, | ||
748 | known::ptr_guaranteed_ne, | ||
749 | known::minnumf32, | ||
750 | known::minnumf64, | ||
751 | known::maxnumf32, | ||
752 | known::rustc_peek, | ||
753 | known::maxnumf64, | ||
754 | known::type_name, | ||
755 | known::variant_count, | ||
756 | ] | ||
757 | .contains(&name) | ||
758 | } | ||
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index 464ffef21..eb41d324e 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs | |||
@@ -172,11 +172,7 @@ pub struct ModuleData { | |||
172 | impl CrateDefMap { | 172 | impl CrateDefMap { |
173 | pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { | 173 | pub(crate) fn crate_def_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { |
174 | let _p = profile::span("crate_def_map_query").detail(|| { | 174 | let _p = profile::span("crate_def_map_query").detail(|| { |
175 | db.crate_graph()[krate] | 175 | db.crate_graph()[krate].display_name.as_deref().unwrap_or_default().to_string() |
176 | .declaration_name | ||
177 | .as_ref() | ||
178 | .map(ToString::to_string) | ||
179 | .unwrap_or_default() | ||
180 | }); | 176 | }); |
181 | let def_map = { | 177 | let def_map = { |
182 | let edition = db.crate_graph()[krate].edition; | 178 | let edition = db.crate_graph()[krate].edition; |
@@ -287,10 +283,11 @@ pub enum ModuleSource { | |||
287 | } | 283 | } |
288 | 284 | ||
289 | mod diagnostics { | 285 | mod diagnostics { |
286 | use cfg::{CfgExpr, CfgOptions}; | ||
290 | use hir_expand::diagnostics::DiagnosticSink; | 287 | use hir_expand::diagnostics::DiagnosticSink; |
291 | use hir_expand::hygiene::Hygiene; | 288 | use hir_expand::hygiene::Hygiene; |
292 | use hir_expand::InFile; | 289 | use hir_expand::InFile; |
293 | use syntax::{ast, AstPtr}; | 290 | use syntax::{ast, AstPtr, SyntaxNodePtr}; |
294 | 291 | ||
295 | use crate::path::ModPath; | 292 | use crate::path::ModPath; |
296 | use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; | 293 | use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; |
@@ -302,6 +299,8 @@ mod diagnostics { | |||
302 | UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, | 299 | UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, |
303 | 300 | ||
304 | UnresolvedImport { ast: AstId<ast::Use>, index: usize }, | 301 | UnresolvedImport { ast: AstId<ast::Use>, index: usize }, |
302 | |||
303 | UnconfiguredCode { ast: InFile<SyntaxNodePtr>, cfg: CfgExpr, opts: CfgOptions }, | ||
305 | } | 304 | } |
306 | 305 | ||
307 | #[derive(Debug, PartialEq, Eq)] | 306 | #[derive(Debug, PartialEq, Eq)] |
@@ -340,6 +339,15 @@ mod diagnostics { | |||
340 | Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } } | 339 | Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } } |
341 | } | 340 | } |
342 | 341 | ||
342 | pub(super) fn unconfigured_code( | ||
343 | container: LocalModuleId, | ||
344 | ast: InFile<SyntaxNodePtr>, | ||
345 | cfg: CfgExpr, | ||
346 | opts: CfgOptions, | ||
347 | ) -> Self { | ||
348 | Self { in_module: container, kind: DiagnosticKind::UnconfiguredCode { ast, cfg, opts } } | ||
349 | } | ||
350 | |||
343 | pub(super) fn add_to( | 351 | pub(super) fn add_to( |
344 | &self, | 352 | &self, |
345 | db: &dyn DefDatabase, | 353 | db: &dyn DefDatabase, |
@@ -389,6 +397,15 @@ mod diagnostics { | |||
389 | sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) }); | 397 | sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) }); |
390 | } | 398 | } |
391 | } | 399 | } |
400 | |||
401 | DiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { | ||
402 | sink.push(InactiveCode { | ||
403 | file: ast.file_id, | ||
404 | node: ast.value.clone(), | ||
405 | cfg: cfg.clone(), | ||
406 | opts: opts.clone(), | ||
407 | }); | ||
408 | } | ||
392 | } | 409 | } |
393 | } | 410 | } |
394 | } | 411 | } |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index c8cd04264..f30172d90 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -6,7 +6,7 @@ | |||
6 | use std::iter; | 6 | use std::iter; |
7 | 7 | ||
8 | use base_db::{CrateId, FileId, ProcMacroId}; | 8 | use base_db::{CrateId, FileId, ProcMacroId}; |
9 | use cfg::CfgOptions; | 9 | use cfg::{CfgExpr, CfgOptions}; |
10 | use hir_expand::InFile; | 10 | use hir_expand::InFile; |
11 | use hir_expand::{ | 11 | use 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,8 +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 | continue; | 917 | if !self.is_cfg_enabled(&cfg) { |
918 | self.emit_unconfigured_diagnostic(item, &cfg); | ||
919 | continue; | ||
920 | } | ||
917 | } | 921 | } |
918 | let module = | 922 | let module = |
919 | 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 }; |
@@ -1320,8 +1324,22 @@ impl ModCollector<'_, '_> { | |||
1320 | } | 1324 | } |
1321 | } | 1325 | } |
1322 | 1326 | ||
1323 | fn is_cfg_enabled(&self, attrs: &Attrs) -> bool { | 1327 | fn is_cfg_enabled(&self, cfg: &CfgExpr) -> bool { |
1324 | attrs.is_cfg_enabled(self.def_collector.cfg_options) | 1328 | self.def_collector.cfg_options.check(cfg) != Some(false) |
1329 | } | ||
1330 | |||
1331 | fn emit_unconfigured_diagnostic(&mut self, item: ModItem, cfg: &CfgExpr) { | ||
1332 | let ast_id = item.ast_id(self.item_tree); | ||
1333 | let id_map = self.def_collector.db.ast_id_map(self.file_id); | ||
1334 | let syntax_ptr = id_map.get(ast_id).syntax_node_ptr(); | ||
1335 | |||
1336 | let ast_node = InFile::new(self.file_id, syntax_ptr); | ||
1337 | self.def_collector.def_map.diagnostics.push(DefDiagnostic::unconfigured_code( | ||
1338 | self.module_id, | ||
1339 | ast_node, | ||
1340 | cfg.clone(), | ||
1341 | self.def_collector.cfg_options.clone(), | ||
1342 | )); | ||
1325 | } | 1343 | } |
1326 | } | 1344 | } |
1327 | 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 @@ | |||
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] |
@@ -129,3 +97,25 @@ fn unresolved_module() { | |||
129 | ", | 97 | ", |
130 | ); | 98 | ); |
131 | } | 99 | } |
100 | |||
101 | #[test] | ||
102 | fn 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; | |||
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 | } |