aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/code_model_api.rs33
-rw-r--r--crates/ra_hir/src/code_model_impl/module.rs19
-rw-r--r--crates/ra_hir/src/diagnostics.rs115
-rw-r--r--crates/ra_hir/src/expr.rs15
-rw-r--r--crates/ra_hir/src/lib.rs3
-rw-r--r--crates/ra_hir/src/mock.rs18
-rw-r--r--crates/ra_hir/src/nameres.rs81
-rw-r--r--crates/ra_hir/src/nameres/collector.rs70
-rw-r--r--crates/ra_hir/src/nameres/tests.rs19
-rw-r--r--crates/ra_hir/src/ty/infer.rs55
-rw-r--r--crates/ra_hir/src/ty/tests.rs24
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs87
-rw-r--r--crates/ra_ide_api/tests/test/main.rs16
-rw-r--r--crates/ra_ide_api/tests/test/snapshots/test__unresolved_module_diagnostic.snap28
-rw-r--r--crates/ra_syntax/src/ptr.rs6
15 files changed, 424 insertions, 165 deletions
diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs
index 45fa4cd11..5437133b8 100644
--- a/crates/ra_hir/src/code_model_api.rs
+++ b/crates/ra_hir/src/code_model_api.rs
@@ -1,8 +1,7 @@
1use std::sync::Arc; 1use std::sync::Arc;
2 2
3use relative_path::RelativePathBuf;
4use ra_db::{CrateId, SourceRootId, Edition}; 3use ra_db::{CrateId, SourceRootId, Edition};
5use ra_syntax::{ast::self, TreeArc, SyntaxNode}; 4use ra_syntax::{ast::self, TreeArc};
6 5
7use crate::{ 6use crate::{
8 Name, ScopesWithSourceMap, Ty, HirFileId, 7 Name, ScopesWithSourceMap, Ty, HirFileId,
@@ -17,6 +16,7 @@ use crate::{
17 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId}, 16 ids::{FunctionId, StructId, EnumId, AstItemDef, ConstId, StaticId, TraitId, TypeId},
18 impl_block::ImplBlock, 17 impl_block::ImplBlock,
19 resolve::Resolver, 18 resolve::Resolver,
19 diagnostics::DiagnosticSink,
20}; 20};
21 21
22/// hir::Crate describes a single crate. It's the main interface with which 22/// hir::Crate describes a single crate. It's the main interface with which
@@ -95,11 +95,6 @@ pub enum ModuleSource {
95 Module(TreeArc<ast::Module>), 95 Module(TreeArc<ast::Module>),
96} 96}
97 97
98#[derive(Clone, Debug, Hash, PartialEq, Eq)]
99pub enum Problem {
100 UnresolvedModule { candidate: RelativePathBuf },
101}
102
103impl Module { 98impl Module {
104 /// Name of this module. 99 /// Name of this module.
105 pub fn name(&self, db: &impl HirDatabase) -> Option<Name> { 100 pub fn name(&self, db: &impl HirDatabase) -> Option<Name> {
@@ -171,8 +166,24 @@ impl Module {
171 db.crate_def_map(self.krate)[self.module_id].scope.clone() 166 db.crate_def_map(self.krate)[self.module_id].scope.clone()
172 } 167 }
173 168
174 pub fn problems(&self, db: &impl HirDatabase) -> Vec<(TreeArc<SyntaxNode>, Problem)> { 169 pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) {
175 self.problems_impl(db) 170 db.crate_def_map(self.krate).add_diagnostics(db, self.module_id, sink);
171 for decl in self.declarations(db) {
172 match decl {
173 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
174 crate::ModuleDef::Module(f) => f.diagnostics(db, sink),
175 _ => (),
176 }
177 }
178
179 for impl_block in self.impl_blocks(db) {
180 for item in impl_block.items(db) {
181 match item {
182 crate::ImplItem::Method(f) => f.diagnostics(db, sink),
183 _ => (),
184 }
185 }
186 }
176 } 187 }
177 188
178 pub fn resolver(&self, db: &impl HirDatabase) -> Resolver { 189 pub fn resolver(&self, db: &impl HirDatabase) -> Resolver {
@@ -519,6 +530,10 @@ impl Function {
519 let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r }; 530 let r = if !p.params.is_empty() { r.push_generic_params_scope(p) } else { r };
520 r 531 r
521 } 532 }
533
534 pub fn diagnostics(&self, db: &impl HirDatabase, sink: &mut DiagnosticSink) {
535 self.infer(db).add_diagnostics(db, *self, sink);
536 }
522} 537}
523 538
524impl Docs for Function { 539impl Docs for Function {
diff --git a/crates/ra_hir/src/code_model_impl/module.rs b/crates/ra_hir/src/code_model_impl/module.rs
index 52a33e981..14237060c 100644
--- a/crates/ra_hir/src/code_model_impl/module.rs
+++ b/crates/ra_hir/src/code_model_impl/module.rs
@@ -1,8 +1,8 @@
1use ra_db::FileId; 1use ra_db::FileId;
2use ra_syntax::{ast, SyntaxNode, TreeArc, AstNode}; 2use ra_syntax::{ast, TreeArc, AstNode};
3 3
4use crate::{ 4use crate::{
5 Module, ModuleSource, Problem, Name, 5 Module, ModuleSource, Name,
6 nameres::{CrateModuleId, ImportId}, 6 nameres::{CrateModuleId, ImportId},
7 HirDatabase, DefDatabase, 7 HirDatabase, DefDatabase,
8 HirFileId, SourceItemId, 8 HirFileId, SourceItemId,
@@ -108,19 +108,4 @@ impl Module {
108 let parent_id = def_map[self.module_id].parent?; 108 let parent_id = def_map[self.module_id].parent?;
109 Some(self.with_module_id(parent_id)) 109 Some(self.with_module_id(parent_id))
110 } 110 }
111
112 pub(crate) fn problems_impl(
113 &self,
114 db: &impl HirDatabase,
115 ) -> Vec<(TreeArc<SyntaxNode>, Problem)> {
116 let def_map = db.crate_def_map(self.krate);
117 let (my_file_id, _) = self.definition_source(db);
118 // FIXME: not entirely corret filterint by module
119 def_map
120 .problems()
121 .iter()
122 .filter(|(source_item_id, _problem)| my_file_id == source_item_id.file_id)
123 .map(|(source_item_id, problem)| (db.file_item(*source_item_id), problem.clone()))
124 .collect()
125 }
126} 111}
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
new file mode 100644
index 000000000..d6a51b833
--- /dev/null
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -0,0 +1,115 @@
1use std::{fmt, any::Any};
2
3use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode};
4use relative_path::RelativePathBuf;
5
6use crate::{HirFileId, HirDatabase};
7
8/// Diagnostic defines hir API for errors and warnings.
9///
10/// It is used as a `dyn` object, which you can downcast to a concrete
11/// diagnostic. DiagnosticSink are structured, meaning that they include rich
12/// information which can be used by IDE to create fixes. DiagnosticSink are
13/// expressed in terms of macro-expanded syntax tree nodes (so, it's a bad idea
14/// to diagnostic in a salsa value).
15///
16/// Internally, various subsystems of hir produce diagnostics specific to a
17/// subsystem (typically, an `enum`), which are safe to store in salsa but do not
18/// include source locations. Such internal diagnostic are transformed into an
19/// instance of `Diagnostic` on demand.
20pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
21 fn message(&self) -> String;
22 fn file(&self) -> HirFileId;
23 fn syntax_node_ptr(&self) -> SyntaxNodePtr;
24 fn highlight_range(&self) -> TextRange {
25 self.syntax_node_ptr().range()
26 }
27 fn as_any(&self) -> &(dyn Any + Send + 'static);
28}
29
30impl dyn Diagnostic {
31 pub fn syntax_node(&self, db: &impl HirDatabase) -> TreeArc<SyntaxNode> {
32 let source_file = db.hir_parse(self.file());
33 self.syntax_node_ptr().to_node(&source_file).to_owned()
34 }
35 pub fn downcast_ref<D: Diagnostic>(&self) -> Option<&D> {
36 self.as_any().downcast_ref()
37 }
38}
39
40pub struct DiagnosticSink<'a> {
41 callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>,
42 default_callback: Box<dyn FnMut(&dyn Diagnostic) + 'a>,
43}
44
45impl<'a> DiagnosticSink<'a> {
46 pub fn new(cb: impl FnMut(&dyn Diagnostic) + 'a) -> DiagnosticSink<'a> {
47 DiagnosticSink { callbacks: Vec::new(), default_callback: Box::new(cb) }
48 }
49
50 pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> DiagnosticSink<'a> {
51 let cb = move |diag: &dyn Diagnostic| match diag.downcast_ref::<D>() {
52 Some(d) => {
53 cb(d);
54 Ok(())
55 }
56 None => Err(()),
57 };
58 self.callbacks.push(Box::new(cb));
59 self
60 }
61
62 pub(crate) fn push(&mut self, d: impl Diagnostic) {
63 let d: &dyn Diagnostic = &d;
64 for cb in self.callbacks.iter_mut() {
65 match cb(d) {
66 Ok(()) => return,
67 Err(()) => (),
68 }
69 }
70 (self.default_callback)(d)
71 }
72}
73
74#[derive(Debug)]
75pub struct NoSuchField {
76 pub file: HirFileId,
77 pub field: AstPtr<ast::NamedField>,
78}
79
80impl Diagnostic for NoSuchField {
81 fn message(&self) -> String {
82 "no such field".to_string()
83 }
84 fn file(&self) -> HirFileId {
85 self.file
86 }
87 fn syntax_node_ptr(&self) -> SyntaxNodePtr {
88 self.field.into()
89 }
90 fn as_any(&self) -> &(Any + Send + 'static) {
91 self
92 }
93}
94
95#[derive(Debug)]
96pub struct UnresolvedModule {
97 pub file: HirFileId,
98 pub decl: AstPtr<ast::Module>,
99 pub candidate: RelativePathBuf,
100}
101
102impl Diagnostic for UnresolvedModule {
103 fn message(&self) -> String {
104 "unresolved module".to_string()
105 }
106 fn file(&self) -> HirFileId {
107 self.file
108 }
109 fn syntax_node_ptr(&self) -> SyntaxNodePtr {
110 self.decl.into()
111 }
112 fn as_any(&self) -> &(Any + Send + 'static) {
113 self
114 }
115}
diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs
index c37fd0454..a85422955 100644
--- a/crates/ra_hir/src/expr.rs
+++ b/crates/ra_hir/src/expr.rs
@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
5 5
6use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; 6use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap};
7use ra_syntax::{ 7use ra_syntax::{
8 SyntaxNodePtr, AstNode, 8 SyntaxNodePtr, AstPtr, AstNode,
9 ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner} 9 ast::{self, LoopBodyOwner, ArgListOwner, NameOwner, LiteralFlavor, TypeAscriptionOwner}
10}; 10};
11 11
@@ -54,6 +54,7 @@ pub struct BodySourceMap {
54 expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>, 54 expr_map_back: ArenaMap<ExprId, SyntaxNodePtr>,
55 pat_map: FxHashMap<SyntaxNodePtr, PatId>, 55 pat_map: FxHashMap<SyntaxNodePtr, PatId>,
56 pat_map_back: ArenaMap<PatId, SyntaxNodePtr>, 56 pat_map_back: ArenaMap<PatId, SyntaxNodePtr>,
57 field_map: FxHashMap<(ExprId, usize), AstPtr<ast::NamedField>>,
57} 58}
58 59
59impl Body { 60impl Body {
@@ -138,6 +139,10 @@ impl BodySourceMap {
138 pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> { 139 pub fn node_pat(&self, node: &ast::Pat) -> Option<PatId> {
139 self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned() 140 self.pat_map.get(&SyntaxNodePtr::new(node.syntax())).cloned()
140 } 141 }
142
143 pub fn field_syntax(&self, expr: ExprId, field: usize) -> AstPtr<ast::NamedField> {
144 self.field_map[&(expr, field)].clone()
145 }
141} 146}
142 147
143#[derive(Debug, Clone, Eq, PartialEq)] 148#[derive(Debug, Clone, Eq, PartialEq)]
@@ -629,8 +634,10 @@ impl ExprCollector {
629 } 634 }
630 ast::ExprKind::StructLit(e) => { 635 ast::ExprKind::StructLit(e) => {
631 let path = e.path().and_then(Path::from_ast); 636 let path = e.path().and_then(Path::from_ast);
637 let mut field_ptrs = Vec::new();
632 let fields = if let Some(nfl) = e.named_field_list() { 638 let fields = if let Some(nfl) = e.named_field_list() {
633 nfl.fields() 639 nfl.fields()
640 .inspect(|field| field_ptrs.push(AstPtr::new(*field)))
634 .map(|field| StructLitField { 641 .map(|field| StructLitField {
635 name: field 642 name: field
636 .name_ref() 643 .name_ref()
@@ -657,7 +664,11 @@ impl ExprCollector {
657 Vec::new() 664 Vec::new()
658 }; 665 };
659 let spread = e.spread().map(|s| self.collect_expr(s)); 666 let spread = e.spread().map(|s| self.collect_expr(s));
660 self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr) 667 let res = self.alloc_expr(Expr::StructLit { path, fields, spread }, syntax_ptr);
668 for (i, ptr) in field_ptrs.into_iter().enumerate() {
669 self.source_map.field_map.insert((res, i), ptr);
670 }
671 res
661 } 672 }
662 ast::ExprKind::FieldExpr(e) => { 673 ast::ExprKind::FieldExpr(e) => {
663 let expr = self.collect_expr_opt(e.expr()); 674 let expr = self.collect_expr_opt(e.expr());
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index a89c916f8..ce54d7608 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -35,6 +35,7 @@ mod expr;
35mod generics; 35mod generics;
36mod docs; 36mod docs;
37mod resolve; 37mod resolve;
38pub mod diagnostics;
38 39
39mod code_model_api; 40mod code_model_api;
40mod code_model_impl; 41mod code_model_impl;
@@ -63,7 +64,7 @@ pub use self::{
63 64
64pub use self::code_model_api::{ 65pub use self::code_model_api::{
65 Crate, CrateDependency, 66 Crate, CrateDependency,
66 Module, ModuleDef, ModuleSource, Problem, 67 Module, ModuleDef, ModuleSource,
67 Struct, Enum, EnumVariant, 68 Struct, Enum, EnumVariant,
68 Function, FnSignature, 69 Function, FnSignature,
69 StructField, FieldSource, 70 StructField, FieldSource,
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index 10d4c1b8c..aeab6b180 100644
--- a/crates/ra_hir/src/mock.rs
+++ b/crates/ra_hir/src/mock.rs
@@ -9,7 +9,7 @@ use relative_path::RelativePathBuf;
9use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset}; 9use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset};
10use rustc_hash::FxHashMap; 10use rustc_hash::FxHashMap;
11 11
12use crate::{db, HirInterner}; 12use crate::{db, HirInterner, diagnostics::DiagnosticSink};
13 13
14pub const WORKSPACE: SourceRootId = SourceRootId(0); 14pub const WORKSPACE: SourceRootId = SourceRootId(0);
15 15
@@ -70,6 +70,22 @@ impl MockDatabase {
70 self.set_crate_graph(Arc::new(crate_graph)) 70 self.set_crate_graph(Arc::new(crate_graph))
71 } 71 }
72 72
73 pub fn diagnostics(&self) -> String {
74 let mut buf = String::from("\n");
75 let mut files: Vec<FileId> = self.files.values().map(|&it| it).collect();
76 files.sort();
77 for file in files {
78 let module = crate::source_binder::module_from_file_id(self, file).unwrap();
79 module.diagnostics(
80 self,
81 &mut DiagnosticSink::new(|d| {
82 buf += &format!("{:?}: {}\n", d.syntax_node(self).text(), d.message());
83 }),
84 )
85 }
86 buf
87 }
88
73 fn from_fixture(fixture: &str) -> (MockDatabase, Option<FilePosition>) { 89 fn from_fixture(fixture: &str) -> (MockDatabase, Option<FilePosition>) {
74 let mut db = MockDatabase::default(); 90 let mut db = MockDatabase::default();
75 91
diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs
index d361cf9e6..56ed872d5 100644
--- a/crates/ra_hir/src/nameres.rs
+++ b/crates/ra_hir/src/nameres.rs
@@ -61,9 +61,11 @@ use ra_db::{FileId, Edition};
61use test_utils::tested_by; 61use test_utils::tested_by;
62 62
63use crate::{ 63use crate::{
64 ModuleDef, Name, Crate, Module, Problem, 64 ModuleDef, Name, Crate, Module,
65 DefDatabase, Path, PathKind, HirFileId, 65 DefDatabase, Path, PathKind, HirFileId,
66 ids::{SourceItemId, SourceFileItemId, MacroCallId}, 66 ids::{SourceItemId, SourceFileItemId, MacroCallId},
67 diagnostics::DiagnosticSink,
68 nameres::diagnostics::DefDiagnostic,
67}; 69};
68 70
69pub(crate) use self::raw::{RawItems, ImportId, ImportSourceMap}; 71pub(crate) use self::raw::{RawItems, ImportId, ImportSourceMap};
@@ -85,7 +87,7 @@ pub struct CrateDefMap {
85 macros: Arena<CrateMacroId, mbe::MacroRules>, 87 macros: Arena<CrateMacroId, mbe::MacroRules>,
86 public_macros: FxHashMap<Name, CrateMacroId>, 88 public_macros: FxHashMap<Name, CrateMacroId>,
87 macro_resolutions: FxHashMap<MacroCallId, (Crate, CrateMacroId)>, 89 macro_resolutions: FxHashMap<MacroCallId, (Crate, CrateMacroId)>,
88 problems: CrateDefMapProblems, 90 diagnostics: Vec<DefDiagnostic>,
89} 91}
90 92
91impl std::ops::Index<CrateModuleId> for CrateDefMap { 93impl std::ops::Index<CrateModuleId> for CrateDefMap {
@@ -125,21 +127,6 @@ pub(crate) struct ModuleData {
125 pub(crate) definition: Option<FileId>, 127 pub(crate) definition: Option<FileId>,
126} 128}
127 129
128#[derive(Default, Debug, PartialEq, Eq)]
129pub(crate) struct CrateDefMapProblems {
130 problems: Vec<(SourceItemId, Problem)>,
131}
132
133impl CrateDefMapProblems {
134 fn add(&mut self, source_item_id: SourceItemId, problem: Problem) {
135 self.problems.push((source_item_id, problem))
136 }
137
138 pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a SourceItemId, &'a Problem)> + 'a {
139 self.problems.iter().map(|(s, p)| (s, p))
140 }
141}
142
143#[derive(Debug, Default, PartialEq, Eq, Clone)] 130#[derive(Debug, Default, PartialEq, Eq, Clone)]
144pub struct ModuleScope { 131pub struct ModuleScope {
145 items: FxHashMap<Name, Resolution>, 132 items: FxHashMap<Name, Resolution>,
@@ -212,7 +199,7 @@ impl CrateDefMap {
212 macros: Arena::default(), 199 macros: Arena::default(),
213 public_macros: FxHashMap::default(), 200 public_macros: FxHashMap::default(),
214 macro_resolutions: FxHashMap::default(), 201 macro_resolutions: FxHashMap::default(),
215 problems: CrateDefMapProblems::default(), 202 diagnostics: Vec::new(),
216 } 203 }
217 }; 204 };
218 let def_map = collector::collect_defs(db, def_map); 205 let def_map = collector::collect_defs(db, def_map);
@@ -224,10 +211,6 @@ impl CrateDefMap {
224 self.root 211 self.root
225 } 212 }
226 213
227 pub(crate) fn problems(&self) -> &CrateDefMapProblems {
228 &self.problems
229 }
230
231 pub(crate) fn mk_module(&self, module_id: CrateModuleId) -> Module { 214 pub(crate) fn mk_module(&self, module_id: CrateModuleId) -> Module {
232 Module { krate: self.krate, module_id } 215 Module { krate: self.krate, module_id }
233 } 216 }
@@ -240,6 +223,15 @@ impl CrateDefMap {
240 &self.extern_prelude 223 &self.extern_prelude
241 } 224 }
242 225
226 pub(crate) fn add_diagnostics(
227 &self,
228 db: &impl DefDatabase,
229 module: CrateModuleId,
230 sink: &mut DiagnosticSink,
231 ) {
232 self.diagnostics.iter().for_each(|it| it.add_to(db, module, sink))
233 }
234
243 pub(crate) fn resolve_macro( 235 pub(crate) fn resolve_macro(
244 &self, 236 &self,
245 macro_call_id: MacroCallId, 237 macro_call_id: MacroCallId,
@@ -452,3 +444,48 @@ impl CrateDefMap {
452 } 444 }
453 } 445 }
454} 446}
447
448mod diagnostics {
449 use relative_path::RelativePathBuf;
450 use ra_syntax::{AstPtr, AstNode, ast};
451
452 use crate::{
453 SourceItemId, DefDatabase,
454 nameres::CrateModuleId,
455 diagnostics::{DiagnosticSink, UnresolvedModule},
456};
457
458 #[derive(Debug, PartialEq, Eq)]
459 pub(super) enum DefDiagnostic {
460 UnresolvedModule {
461 module: CrateModuleId,
462 declaration: SourceItemId,
463 candidate: RelativePathBuf,
464 },
465 }
466
467 impl DefDiagnostic {
468 pub(super) fn add_to(
469 &self,
470 db: &impl DefDatabase,
471 target_module: CrateModuleId,
472 sink: &mut DiagnosticSink,
473 ) {
474 match self {
475 DefDiagnostic::UnresolvedModule { module, declaration, candidate } => {
476 if *module != target_module {
477 return;
478 }
479 let syntax = db.file_item(*declaration);
480 let decl = ast::Module::cast(&syntax).unwrap();
481 sink.push(UnresolvedModule {
482 file: declaration.file_id,
483 decl: AstPtr::new(&decl),
484 candidate: candidate.clone(),
485 })
486 }
487 }
488 }
489 }
490
491}
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index c5b73cfbe..8830b4624 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -6,14 +6,17 @@ use ra_db::FileId;
6 6
7use crate::{ 7use crate::{
8 Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, 8 Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias,
9 DefDatabase, HirFileId, Name, Path, Problem, Crate, 9 DefDatabase, HirFileId, Name, Path, Crate,
10 KnownName, 10 KnownName,
11 nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode, raw}, 11 nameres::{
12 Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode,
13 CrateDefMap, CrateModuleId, ModuleData, CrateMacroId,
14 diagnostics::DefDiagnostic,
15 raw,
16 },
12 ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, 17 ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId},
13}; 18};
14 19
15use super::{CrateDefMap, CrateModuleId, ModuleData, CrateMacroId};
16
17pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { 20pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap {
18 // populate external prelude 21 // populate external prelude
19 for dep in def_map.krate.dependencies(db) { 22 for dep in def_map.krate.dependencies(db) {
@@ -405,25 +408,27 @@ where
405 raw::ModuleData::Declaration { name, source_item_id } => { 408 raw::ModuleData::Declaration { name, source_item_id } => {
406 let source_item_id = source_item_id.with_file_id(self.file_id); 409 let source_item_id = source_item_id.with_file_id(self.file_id);
407 let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none(); 410 let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none();
408 let (file_ids, problem) = 411 match resolve_submodule(self.def_collector.db, self.file_id, name, is_root) {
409 resolve_submodule(self.def_collector.db, self.file_id, name, is_root); 412 Ok(file_id) => {
410 413 let module_id =
411 if let Some(problem) = problem { 414 self.push_child_module(name.clone(), source_item_id, Some(file_id));
412 self.def_collector.def_map.problems.add(source_item_id, problem) 415 let raw_items = self.def_collector.db.raw_items(file_id);
413 } 416 ModCollector {
414 417 def_collector: &mut *self.def_collector,
415 if let Some(&file_id) = file_ids.first() { 418 module_id,
416 let module_id = 419 file_id: file_id.into(),
417 self.push_child_module(name.clone(), source_item_id, Some(file_id)); 420 raw_items: &raw_items,
418 let raw_items = self.def_collector.db.raw_items(file_id); 421 }
419 ModCollector { 422 .collect(raw_items.items())
420 def_collector: &mut *self.def_collector,
421 module_id,
422 file_id: file_id.into(),
423 raw_items: &raw_items,
424 } 423 }
425 .collect(raw_items.items()) 424 Err(candidate) => self.def_collector.def_map.diagnostics.push(
426 } 425 DefDiagnostic::UnresolvedModule {
426 module: self.module_id,
427 declaration: source_item_id,
428 candidate,
429 },
430 ),
431 };
427 } 432 }
428 } 433 }
429 } 434 }
@@ -524,7 +529,7 @@ fn resolve_submodule(
524 file_id: HirFileId, 529 file_id: HirFileId,
525 name: &Name, 530 name: &Name,
526 is_root: bool, 531 is_root: bool,
527) -> (Vec<FileId>, Option<Problem>) { 532) -> Result<FileId, RelativePathBuf> {
528 // FIXME: handle submodules of inline modules properly 533 // FIXME: handle submodules of inline modules properly
529 let file_id = file_id.original_file(db); 534 let file_id = file_id.original_file(db);
530 let source_root_id = db.file_source_root(file_id); 535 let source_root_id = db.file_source_root(file_id);
@@ -545,17 +550,10 @@ fn resolve_submodule(
545 candidates.push(file_dir_mod.clone()); 550 candidates.push(file_dir_mod.clone());
546 }; 551 };
547 let sr = db.source_root(source_root_id); 552 let sr = db.source_root(source_root_id);
548 let points_to = candidates 553 let mut points_to = candidates.into_iter().filter_map(|path| sr.files.get(&path)).map(|&it| it);
549 .into_iter() 554 // FIXME: handle ambiguity
550 .filter_map(|path| sr.files.get(&path)) 555 match points_to.next() {
551 .map(|&it| it) 556 Some(file_id) => Ok(file_id),
552 .collect::<Vec<_>>(); 557 None => Err(if is_dir_owner { file_mod } else { file_dir_mod }),
553 let problem = if points_to.is_empty() { 558 }
554 Some(Problem::UnresolvedModule {
555 candidate: if is_dir_owner { file_mod } else { file_dir_mod },
556 })
557 } else {
558 None
559 };
560 (points_to, problem)
561} 559}
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs
index ac9b88520..572bd1bf7 100644
--- a/crates/ra_hir/src/nameres/tests.rs
+++ b/crates/ra_hir/src/nameres/tests.rs
@@ -552,3 +552,22 @@ foo: v
552"### 552"###
553 ); 553 );
554} 554}
555
556#[test]
557fn unresolved_module_diagnostics() {
558 let diagnostics = MockDatabase::with_files(
559 r"
560 //- /lib.rs
561 mod foo;
562 mod bar;
563 mod baz {}
564 //- /foo.rs
565 ",
566 )
567 .diagnostics();
568
569 assert_snapshot_matches!(diagnostics, @r###"
570"mod bar;": unresolved module
571"###
572 );
573}
diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs
index cff7e7481..5fd602a9e 100644
--- a/crates/ra_hir/src/ty/infer.rs
+++ b/crates/ra_hir/src/ty/infer.rs
@@ -36,7 +36,9 @@ use crate::{
36 path::{GenericArgs, GenericArg}, 36 path::{GenericArgs, GenericArg},
37 adt::VariantDef, 37 adt::VariantDef,
38 resolve::{Resolver, Resolution}, 38 resolve::{Resolver, Resolution},
39 nameres::Namespace 39 nameres::Namespace,
40 ty::infer::diagnostics::InferenceDiagnostic,
41 diagnostics::DiagnosticSink,
40}; 42};
41use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor}; 43use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor};
42 44
@@ -96,6 +98,7 @@ pub struct InferenceResult {
96 field_resolutions: FxHashMap<ExprId, StructField>, 98 field_resolutions: FxHashMap<ExprId, StructField>,
97 /// For each associated item record what it resolves to 99 /// For each associated item record what it resolves to
98 assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, 100 assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
101 diagnostics: Vec<InferenceDiagnostic>,
99 pub(super) type_of_expr: ArenaMap<ExprId, Ty>, 102 pub(super) type_of_expr: ArenaMap<ExprId, Ty>,
100 pub(super) type_of_pat: ArenaMap<PatId, Ty>, 103 pub(super) type_of_pat: ArenaMap<PatId, Ty>,
101} 104}
@@ -113,6 +116,14 @@ impl InferenceResult {
113 pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> { 116 pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option<ImplItem> {
114 self.assoc_resolutions.get(&id.into()).map(|it| *it) 117 self.assoc_resolutions.get(&id.into()).map(|it| *it)
115 } 118 }
119 pub(crate) fn add_diagnostics(
120 &self,
121 db: &impl HirDatabase,
122 owner: Function,
123 sink: &mut DiagnosticSink,
124 ) {
125 self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink))
126 }
116} 127}
117 128
118impl Index<ExprId> for InferenceResult { 129impl Index<ExprId> for InferenceResult {
@@ -143,6 +154,7 @@ struct InferenceContext<'a, D: HirDatabase> {
143 assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>, 154 assoc_resolutions: FxHashMap<ExprOrPatId, ImplItem>,
144 type_of_expr: ArenaMap<ExprId, Ty>, 155 type_of_expr: ArenaMap<ExprId, Ty>,
145 type_of_pat: ArenaMap<PatId, Ty>, 156 type_of_pat: ArenaMap<PatId, Ty>,
157 diagnostics: Vec<InferenceDiagnostic>,
146 /// The return type of the function being inferred. 158 /// The return type of the function being inferred.
147 return_ty: Ty, 159 return_ty: Ty,
148} 160}
@@ -155,6 +167,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
155 assoc_resolutions: FxHashMap::default(), 167 assoc_resolutions: FxHashMap::default(),
156 type_of_expr: ArenaMap::default(), 168 type_of_expr: ArenaMap::default(),
157 type_of_pat: ArenaMap::default(), 169 type_of_pat: ArenaMap::default(),
170 diagnostics: Vec::default(),
158 var_unification_table: InPlaceUnificationTable::new(), 171 var_unification_table: InPlaceUnificationTable::new(),
159 return_ty: Ty::Unknown, // set in collect_fn_signature 172 return_ty: Ty::Unknown, // set in collect_fn_signature
160 db, 173 db,
@@ -181,6 +194,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
181 assoc_resolutions: self.assoc_resolutions, 194 assoc_resolutions: self.assoc_resolutions,
182 type_of_expr: expr_types, 195 type_of_expr: expr_types,
183 type_of_pat: pat_types, 196 type_of_pat: pat_types,
197 diagnostics: self.diagnostics,
184 } 198 }
185 } 199 }
186 200
@@ -915,9 +929,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
915 Expr::StructLit { path, fields, spread } => { 929 Expr::StructLit { path, fields, spread } => {
916 let (ty, def_id) = self.resolve_variant(path.as_ref()); 930 let (ty, def_id) = self.resolve_variant(path.as_ref());
917 let substs = ty.substs().unwrap_or_else(Substs::empty); 931 let substs = ty.substs().unwrap_or_else(Substs::empty);
918 for field in fields { 932 for (field_idx, field) in fields.into_iter().enumerate() {
919 let field_ty = def_id 933 let field_ty = def_id
920 .and_then(|it| it.field(self.db, &field.name)) 934 .and_then(|it| match it.field(self.db, &field.name) {
935 Some(field) => Some(field),
936 None => {
937 self.diagnostics.push(InferenceDiagnostic::NoSuchField {
938 expr: tgt_expr,
939 field: field_idx,
940 });
941 None
942 }
943 })
921 .map_or(Ty::Unknown, |field| field.ty(self.db)) 944 .map_or(Ty::Unknown, |field| field.ty(self.db))
922 .subst(&substs); 945 .subst(&substs);
923 self.infer_expr(field.expr, &Expectation::has_type(field_ty)); 946 self.infer_expr(field.expr, &Expectation::has_type(field_ty));
@@ -1244,3 +1267,29 @@ impl Expectation {
1244 Expectation { ty: Ty::Unknown } 1267 Expectation { ty: Ty::Unknown }
1245 } 1268 }
1246} 1269}
1270
1271mod diagnostics {
1272 use crate::{expr::ExprId, diagnostics::{DiagnosticSink, NoSuchField}, HirDatabase, Function};
1273
1274 #[derive(Debug, PartialEq, Eq, Clone)]
1275 pub(super) enum InferenceDiagnostic {
1276 NoSuchField { expr: ExprId, field: usize },
1277 }
1278
1279 impl InferenceDiagnostic {
1280 pub(super) fn add_to(
1281 &self,
1282 db: &impl HirDatabase,
1283 owner: Function,
1284 sink: &mut DiagnosticSink,
1285 ) {
1286 match self {
1287 InferenceDiagnostic::NoSuchField { expr, field } => {
1288 let (file, _) = owner.source(db);
1289 let field = owner.body_source_map(db).field_syntax(*expr, *field);
1290 sink.push(NoSuchField { file, field })
1291 }
1292 }
1293 }
1294 }
1295}
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index 5d8ad4aa7..3aedba243 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -2319,3 +2319,27 @@ fn typing_whitespace_inside_a_function_should_not_invalidate_types() {
2319 assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events) 2319 assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events)
2320 } 2320 }
2321} 2321}
2322
2323#[test]
2324fn no_such_field_diagnostics() {
2325 let diagnostics = MockDatabase::with_files(
2326 r"
2327 //- /lib.rs
2328 struct S { foo: i32, bar: () }
2329 impl S {
2330 fn new() -> S {
2331 S {
2332 foo: 92,
2333 baz: 62,
2334 }
2335 }
2336 }
2337 ",
2338 )
2339 .diagnostics();
2340
2341 assert_snapshot_matches!(diagnostics, @r###"
2342"baz: 62": no such field
2343"###
2344 );
2345}
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index 156f28ca3..5a78e94d8 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -1,10 +1,11 @@
1use std::cell::RefCell;
2
1use itertools::Itertools; 3use itertools::Itertools;
2use hir::{Problem, source_binder}; 4use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}};
3use ra_db::SourceDatabase; 5use ra_db::SourceDatabase;
4use ra_syntax::{ 6use ra_syntax::{
5 Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, 7 Location, SourceFile, SyntaxKind, TextRange, SyntaxNode,
6 ast::{self, AstNode}, 8 ast::{self, AstNode},
7
8}; 9};
9use ra_text_edit::{TextEdit, TextEditBuilder}; 10use ra_text_edit::{TextEdit, TextEditBuilder};
10 11
@@ -26,11 +27,31 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
26 check_unnecessary_braces_in_use_statement(&mut res, file_id, node); 27 check_unnecessary_braces_in_use_statement(&mut res, file_id, node);
27 check_struct_shorthand_initialization(&mut res, file_id, node); 28 check_struct_shorthand_initialization(&mut res, file_id, node);
28 } 29 }
29 30 let res = RefCell::new(res);
31 let mut sink = DiagnosticSink::new(|d| {
32 res.borrow_mut().push(Diagnostic {
33 message: d.message(),
34 range: d.highlight_range(),
35 severity: Severity::Error,
36 fix: None,
37 })
38 })
39 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
40 let source_root = db.file_source_root(d.file().original_file(db));
41 let create_file = FileSystemEdit::CreateFile { source_root, path: d.candidate.clone() };
42 let fix = SourceChange::file_system_edit("create module", create_file);
43 res.borrow_mut().push(Diagnostic {
44 range: d.highlight_range(),
45 message: d.message(),
46 severity: Severity::Error,
47 fix: Some(fix),
48 })
49 });
30 if let Some(m) = source_binder::module_from_file_id(db, file_id) { 50 if let Some(m) = source_binder::module_from_file_id(db, file_id) {
31 check_module(&mut res, db, file_id, m); 51 m.diagnostics(db, &mut sink);
32 }; 52 };
33 res 53 drop(sink);
54 res.into_inner()
34} 55}
35 56
36fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { 57fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) {
@@ -128,34 +149,12 @@ fn check_struct_shorthand_initialization(
128 Some(()) 149 Some(())
129} 150}
130 151
131fn check_module(
132 acc: &mut Vec<Diagnostic>,
133 db: &RootDatabase,
134 file_id: FileId,
135 module: hir::Module,
136) {
137 let source_root = db.file_source_root(file_id);
138 for (name_node, problem) in module.problems(db) {
139 let diag = match problem {
140 Problem::UnresolvedModule { candidate } => {
141 let create_file =
142 FileSystemEdit::CreateFile { source_root, path: candidate.clone() };
143 let fix = SourceChange::file_system_edit("create module", create_file);
144 Diagnostic {
145 range: name_node.range(),
146 message: "unresolved module".to_string(),
147 severity: Severity::Error,
148 fix: Some(fix),
149 }
150 }
151 };
152 acc.push(diag)
153 }
154}
155
156#[cfg(test)] 152#[cfg(test)]
157mod tests { 153mod tests {
158 use test_utils::assert_eq_text; 154 use test_utils::assert_eq_text;
155 use insta::assert_debug_snapshot_matches;
156
157 use crate::mock_analysis::single_file;
159 158
160 use super::*; 159 use super::*;
161 160
@@ -185,6 +184,34 @@ mod tests {
185 } 184 }
186 185
187 #[test] 186 #[test]
187 fn test_unresolved_module_diagnostic() {
188 let (analysis, file_id) = single_file("mod foo;");
189 let diagnostics = analysis.diagnostics(file_id).unwrap();
190 assert_debug_snapshot_matches!(diagnostics, @r####"[
191 Diagnostic {
192 message: "unresolved module",
193 range: [0; 8),
194 fix: Some(
195 SourceChange {
196 label: "create module",
197 source_file_edits: [],
198 file_system_edits: [
199 CreateFile {
200 source_root: SourceRootId(
201 0
202 ),
203 path: "foo.rs"
204 }
205 ],
206 cursor_position: None
207 }
208 ),
209 severity: Error
210 }
211]"####);
212 }
213
214 #[test]
188 fn test_check_unnecessary_braces_in_use_statement() { 215 fn test_check_unnecessary_braces_in_use_statement() {
189 check_not_applicable( 216 check_not_applicable(
190 " 217 "
diff --git a/crates/ra_ide_api/tests/test/main.rs b/crates/ra_ide_api/tests/test/main.rs
index 0f0766f62..d4ff21c09 100644
--- a/crates/ra_ide_api/tests/test/main.rs
+++ b/crates/ra_ide_api/tests/test/main.rs
@@ -1,4 +1,3 @@
1use insta::assert_debug_snapshot_matches;
2use ra_ide_api::{ 1use ra_ide_api::{
3 mock_analysis::{single_file, single_file_with_position, single_file_with_range, MockAnalysis}, 2 mock_analysis::{single_file, single_file_with_position, single_file_with_range, MockAnalysis},
4 AnalysisChange, CrateGraph, Edition::Edition2018, Query, NavigationTarget, 3 AnalysisChange, CrateGraph, Edition::Edition2018, Query, NavigationTarget,
@@ -7,21 +6,6 @@ use ra_ide_api::{
7use ra_syntax::SmolStr; 6use ra_syntax::SmolStr;
8 7
9#[test] 8#[test]
10fn test_unresolved_module_diagnostic() {
11 let (analysis, file_id) = single_file("mod foo;");
12 let diagnostics = analysis.diagnostics(file_id).unwrap();
13 assert_debug_snapshot_matches!("unresolved_module_diagnostic", &diagnostics);
14}
15
16// FIXME: move this test to hir
17#[test]
18fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() {
19 let (analysis, file_id) = single_file("mod foo {}");
20 let diagnostics = analysis.diagnostics(file_id).unwrap();
21 assert!(diagnostics.is_empty());
22}
23
24#[test]
25fn test_resolve_crate_root() { 9fn test_resolve_crate_root() {
26 let mock = MockAnalysis::with_files( 10 let mock = MockAnalysis::with_files(
27 " 11 "
diff --git a/crates/ra_ide_api/tests/test/snapshots/test__unresolved_module_diagnostic.snap b/crates/ra_ide_api/tests/test/snapshots/test__unresolved_module_diagnostic.snap
deleted file mode 100644
index 5bb953892..000000000
--- a/crates/ra_ide_api/tests/test/snapshots/test__unresolved_module_diagnostic.snap
+++ /dev/null
@@ -1,28 +0,0 @@
1---
2created: "2019-01-22T14:45:01.486985900+00:00"
3creator: [email protected]
4expression: "&diagnostics"
5source: "crates\\ra_ide_api\\tests\\test\\main.rs"
6---
7[
8 Diagnostic {
9 message: "unresolved module",
10 range: [0; 8),
11 fix: Some(
12 SourceChange {
13 label: "create module",
14 source_file_edits: [],
15 file_system_edits: [
16 CreateFile {
17 source_root: SourceRootId(
18 0
19 ),
20 path: "foo.rs"
21 }
22 ],
23 cursor_position: None
24 }
25 ),
26 severity: Error
27 }
28]
diff --git a/crates/ra_syntax/src/ptr.rs b/crates/ra_syntax/src/ptr.rs
index aae590cb6..d8de1c4c1 100644
--- a/crates/ra_syntax/src/ptr.rs
+++ b/crates/ra_syntax/src/ptr.rs
@@ -64,6 +64,12 @@ impl<N: AstNode> AstPtr<N> {
64 } 64 }
65} 65}
66 66
67impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr {
68 fn from(ptr: AstPtr<N>) -> SyntaxNodePtr {
69 ptr.raw
70 }
71}
72
67#[test] 73#[test]
68fn test_local_syntax_ptr() { 74fn test_local_syntax_ptr() {
69 use crate::{ast, AstNode}; 75 use crate::{ast, AstNode};