diff options
Diffstat (limited to 'crates')
29 files changed, 673 insertions, 316 deletions
diff --git a/crates/ra_assists/src/flip_eq_operands.rs b/crates/ra_assists/src/flip_eq_operands.rs new file mode 100644 index 000000000..df0bb689d --- /dev/null +++ b/crates/ra_assists/src/flip_eq_operands.rs | |||
@@ -0,0 +1,86 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::ast::{AstNode, BinExpr, BinOp}; | ||
3 | |||
4 | use crate::{AssistCtx, Assist, AssistId}; | ||
5 | |||
6 | pub(crate) fn flip_eq_operands(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
7 | let expr = ctx.node_at_offset::<BinExpr>()?; | ||
8 | let lhs = expr.lhs()?.syntax(); | ||
9 | let rhs = expr.rhs()?.syntax(); | ||
10 | let op_range = expr.op()?.range(); | ||
11 | let cursor_in_range = ctx.frange.range.is_subrange(&op_range); | ||
12 | let allowed_ops = [BinOp::EqualityTest, BinOp::NegatedEqualityTest]; | ||
13 | let expr_op = expr.op_kind()?; | ||
14 | if !cursor_in_range || !allowed_ops.iter().any(|o| *o == expr_op) { | ||
15 | return None; | ||
16 | } | ||
17 | ctx.add_action(AssistId("flip_eq_operands"), "flip equality operands", |edit| { | ||
18 | edit.target(op_range); | ||
19 | edit.replace(lhs.range(), rhs.text()); | ||
20 | edit.replace(rhs.range(), lhs.text()); | ||
21 | }); | ||
22 | |||
23 | ctx.build() | ||
24 | } | ||
25 | |||
26 | #[cfg(test)] | ||
27 | mod tests { | ||
28 | use super::*; | ||
29 | |||
30 | use crate::helpers::{check_assist, check_assist_target}; | ||
31 | |||
32 | #[test] | ||
33 | fn flip_eq_operands_for_simple_stmt() { | ||
34 | check_assist( | ||
35 | flip_eq_operands, | ||
36 | "fn f() { let res = 1 ==<|> 2; }", | ||
37 | "fn f() { let res = 2 ==<|> 1; }", | ||
38 | ) | ||
39 | } | ||
40 | |||
41 | #[test] | ||
42 | fn flip_neq_operands_for_simple_stmt() { | ||
43 | check_assist( | ||
44 | flip_eq_operands, | ||
45 | "fn f() { let res = 1 !=<|> 2; }", | ||
46 | "fn f() { let res = 2 !=<|> 1; }", | ||
47 | ) | ||
48 | } | ||
49 | |||
50 | #[test] | ||
51 | fn flip_eq_operands_for_complex_stmt() { | ||
52 | check_assist( | ||
53 | flip_eq_operands, | ||
54 | "fn f() { let res = (1 + 1) ==<|> (2 + 2); }", | ||
55 | "fn f() { let res = (2 + 2) ==<|> (1 + 1); }", | ||
56 | ) | ||
57 | } | ||
58 | |||
59 | #[test] | ||
60 | fn flip_eq_operands_in_match_expr() { | ||
61 | check_assist( | ||
62 | flip_eq_operands, | ||
63 | r#" | ||
64 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { | ||
65 | match other.downcast_ref::<Self>() { | ||
66 | None => false, | ||
67 | Some(it) => it ==<|> self, | ||
68 | } | ||
69 | } | ||
70 | "#, | ||
71 | r#" | ||
72 | fn dyn_eq(&self, other: &dyn Diagnostic) -> bool { | ||
73 | match other.downcast_ref::<Self>() { | ||
74 | None => false, | ||
75 | Some(it) => self ==<|> it, | ||
76 | } | ||
77 | } | ||
78 | "#, | ||
79 | ) | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn flip_eq_operands_target() { | ||
84 | check_assist_target(flip_eq_operands, "fn f() { let res = 1 ==<|> 2; }", "==") | ||
85 | } | ||
86 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 378470ac8..2e47b5215 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -88,6 +88,7 @@ where | |||
88 | mod add_derive; | 88 | mod add_derive; |
89 | mod add_impl; | 89 | mod add_impl; |
90 | mod flip_comma; | 90 | mod flip_comma; |
91 | mod flip_eq_operands; | ||
91 | mod change_visibility; | 92 | mod change_visibility; |
92 | mod fill_match_arms; | 93 | mod fill_match_arms; |
93 | mod fill_struct_fields; | 94 | mod fill_struct_fields; |
@@ -107,6 +108,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis | |||
107 | fill_match_arms::fill_match_arms, | 108 | fill_match_arms::fill_match_arms, |
108 | fill_struct_fields::fill_struct_fields, | 109 | fill_struct_fields::fill_struct_fields, |
109 | flip_comma::flip_comma, | 110 | flip_comma::flip_comma, |
111 | flip_eq_operands::flip_eq_operands, | ||
110 | introduce_variable::introduce_variable, | 112 | introduce_variable::introduce_variable, |
111 | replace_if_let_with_match::replace_if_let_with_match, | 113 | replace_if_let_with_match::replace_if_let_with_match, |
112 | split_import::split_import, | 114 | split_import::split_import, |
diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml index 4c666f556..467628236 100644 --- a/crates/ra_cli/Cargo.toml +++ b/crates/ra_cli/Cargo.toml | |||
@@ -14,7 +14,6 @@ indicatif = "0.11.0" | |||
14 | 14 | ||
15 | ra_syntax = { path = "../ra_syntax" } | 15 | ra_syntax = { path = "../ra_syntax" } |
16 | ra_ide_api = { path = "../ra_ide_api" } | 16 | ra_ide_api = { path = "../ra_ide_api" } |
17 | ra_ide_api_light = { path = "../ra_ide_api_light" } | ||
18 | tools = { path = "../tools" } | 17 | tools = { path = "../tools" } |
19 | ra_batch = { path = "../ra_batch" } | 18 | ra_batch = { path = "../ra_batch" } |
20 | ra_hir = { path = "../ra_hir" } | 19 | ra_hir = { path = "../ra_hir" } |
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 5285f1f28..11f5541eb 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs | |||
@@ -5,7 +5,7 @@ use std::{fs, io::Read, path::Path, time::Instant}; | |||
5 | use clap::{App, Arg, SubCommand}; | 5 | use clap::{App, Arg, SubCommand}; |
6 | use join_to_string::join; | 6 | use join_to_string::join; |
7 | use ra_ide_api::{Analysis, FileRange}; | 7 | use ra_ide_api::{Analysis, FileRange}; |
8 | use ra_ide_api_light::file_structure; | 8 | use ra_ide_api::file_structure; |
9 | use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; | 9 | use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; |
10 | use tools::collect_tests; | 10 | use tools::collect_tests; |
11 | use flexi_logger::Logger; | 11 | use flexi_logger::Logger; |
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 @@ | |||
1 | use std::sync::Arc; | 1 | use std::sync::Arc; |
2 | 2 | ||
3 | use relative_path::RelativePathBuf; | ||
4 | use ra_db::{CrateId, SourceRootId, Edition}; | 3 | use ra_db::{CrateId, SourceRootId, Edition}; |
5 | use ra_syntax::{ast::self, TreeArc, SyntaxNode}; | 4 | use ra_syntax::{ast::self, TreeArc}; |
6 | 5 | ||
7 | use crate::{ | 6 | use 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)] | ||
99 | pub enum Problem { | ||
100 | UnresolvedModule { candidate: RelativePathBuf }, | ||
101 | } | ||
102 | |||
103 | impl Module { | 98 | impl 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 | ||
524 | impl Docs for Function { | 539 | impl 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 @@ | |||
1 | use ra_db::FileId; | 1 | use ra_db::FileId; |
2 | use ra_syntax::{ast, SyntaxNode, TreeArc, AstNode}; | 2 | use ra_syntax::{ast, TreeArc, AstNode}; |
3 | 3 | ||
4 | use crate::{ | 4 | use 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 @@ | |||
1 | use std::{fmt, any::Any}; | ||
2 | |||
3 | use ra_syntax::{SyntaxNodePtr, TreeArc, AstPtr, TextRange, ast, SyntaxNode}; | ||
4 | use relative_path::RelativePathBuf; | ||
5 | |||
6 | use 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. | ||
20 | pub 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 | |||
30 | impl 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 | |||
40 | pub struct DiagnosticSink<'a> { | ||
41 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | ||
42 | default_callback: Box<dyn FnMut(&dyn Diagnostic) + 'a>, | ||
43 | } | ||
44 | |||
45 | impl<'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)] | ||
75 | pub struct NoSuchField { | ||
76 | pub file: HirFileId, | ||
77 | pub field: AstPtr<ast::NamedField>, | ||
78 | } | ||
79 | |||
80 | impl 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)] | ||
96 | pub struct UnresolvedModule { | ||
97 | pub file: HirFileId, | ||
98 | pub decl: AstPtr<ast::Module>, | ||
99 | pub candidate: RelativePathBuf, | ||
100 | } | ||
101 | |||
102 | impl 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 703d99d9b..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 | ||
6 | use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; | 6 | use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; |
7 | use ra_syntax::{ | 7 | use 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 | ||
59 | impl Body { | 60 | impl 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()); |
@@ -680,7 +691,7 @@ impl ExprCollector { | |||
680 | } | 691 | } |
681 | ast::ExprKind::PrefixExpr(e) => { | 692 | ast::ExprKind::PrefixExpr(e) => { |
682 | let expr = self.collect_expr_opt(e.expr()); | 693 | let expr = self.collect_expr_opt(e.expr()); |
683 | if let Some(op) = e.op() { | 694 | if let Some(op) = e.op_kind() { |
684 | self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr) | 695 | self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr) |
685 | } else { | 696 | } else { |
686 | self.alloc_expr(Expr::Missing, syntax_ptr) | 697 | self.alloc_expr(Expr::Missing, syntax_ptr) |
@@ -703,7 +714,7 @@ impl ExprCollector { | |||
703 | ast::ExprKind::BinExpr(e) => { | 714 | ast::ExprKind::BinExpr(e) => { |
704 | let lhs = self.collect_expr_opt(e.lhs()); | 715 | let lhs = self.collect_expr_opt(e.lhs()); |
705 | let rhs = self.collect_expr_opt(e.rhs()); | 716 | let rhs = self.collect_expr_opt(e.rhs()); |
706 | let op = e.op(); | 717 | let op = e.op_kind(); |
707 | self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr) | 718 | self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr) |
708 | } | 719 | } |
709 | ast::ExprKind::TupleExpr(e) => { | 720 | ast::ExprKind::TupleExpr(e) => { |
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; | |||
35 | mod generics; | 35 | mod generics; |
36 | mod docs; | 36 | mod docs; |
37 | mod resolve; | 37 | mod resolve; |
38 | pub mod diagnostics; | ||
38 | 39 | ||
39 | mod code_model_api; | 40 | mod code_model_api; |
40 | mod code_model_impl; | 41 | mod code_model_impl; |
@@ -63,7 +64,7 @@ pub use self::{ | |||
63 | 64 | ||
64 | pub use self::code_model_api::{ | 65 | pub 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; | |||
9 | use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset}; | 9 | use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset}; |
10 | use rustc_hash::FxHashMap; | 10 | use rustc_hash::FxHashMap; |
11 | 11 | ||
12 | use crate::{db, HirInterner}; | 12 | use crate::{db, HirInterner, diagnostics::DiagnosticSink}; |
13 | 13 | ||
14 | pub const WORKSPACE: SourceRootId = SourceRootId(0); | 14 | pub 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}; | |||
61 | use test_utils::tested_by; | 61 | use test_utils::tested_by; |
62 | 62 | ||
63 | use crate::{ | 63 | use 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 | ||
69 | pub(crate) use self::raw::{RawItems, ImportId, ImportSourceMap}; | 71 | pub(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 | ||
91 | impl std::ops::Index<CrateModuleId> for CrateDefMap { | 93 | impl 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)] | ||
129 | pub(crate) struct CrateDefMapProblems { | ||
130 | problems: Vec<(SourceItemId, Problem)>, | ||
131 | } | ||
132 | |||
133 | impl 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)] |
144 | pub struct ModuleScope { | 131 | pub 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 | |||
448 | mod 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 | ||
7 | use crate::{ | 7 | use 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 | ||
15 | use super::{CrateDefMap, CrateModuleId, ModuleData, CrateMacroId}; | ||
16 | |||
17 | pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { | 20 | pub(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] | ||
557 | fn 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 | }; |
41 | use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor}; | 43 | use 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 | ||
118 | impl Index<ExprId> for InferenceResult { | 129 | impl 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 | |||
1271 | mod 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] | ||
2324 | fn 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/Cargo.toml b/crates/ra_ide_api/Cargo.toml index c64226801..45bab4e28 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml | |||
@@ -20,7 +20,6 @@ jemallocator = { version = "0.1.9", optional = true } | |||
20 | jemalloc-ctl = { version = "0.2.0", optional = true } | 20 | jemalloc-ctl = { version = "0.2.0", optional = true } |
21 | 21 | ||
22 | ra_syntax = { path = "../ra_syntax" } | 22 | ra_syntax = { path = "../ra_syntax" } |
23 | ra_ide_api_light = { path = "../ra_ide_api_light" } | ||
24 | ra_text_edit = { path = "../ra_text_edit" } | 23 | ra_text_edit = { path = "../ra_text_edit" } |
25 | ra_db = { path = "../ra_db" } | 24 | ra_db = { path = "../ra_db" } |
26 | ra_fmt = { path = "../ra_fmt" } | 25 | ra_fmt = { path = "../ra_fmt" } |
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs index 3c0475a51..355c0a42a 100644 --- a/crates/ra_ide_api/src/assists.rs +++ b/crates/ra_ide_api/src/assists.rs | |||
@@ -17,14 +17,9 @@ pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | |||
17 | let file_id = frange.file_id; | 17 | let file_id = frange.file_id; |
18 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 18 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; |
19 | let id = label.id; | 19 | let id = label.id; |
20 | let change = SourceChange { | 20 | let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( |
21 | label: label.label, | 21 | action.cursor_position.map(|offset| FilePosition { offset, file_id }), |
22 | source_file_edits: vec![file_edit], | 22 | ); |
23 | file_system_edits: vec![], | ||
24 | cursor_position: action | ||
25 | .cursor_position | ||
26 | .map(|offset| FilePosition { offset, file_id }), | ||
27 | }; | ||
28 | Assist { id, change } | 23 | Assist { id, change } |
29 | }) | 24 | }) |
30 | .collect() | 25 | .collect() |
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index b9dc424c6..5a78e94d8 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | use std::cell::RefCell; | ||
2 | |||
1 | use itertools::Itertools; | 3 | use itertools::Itertools; |
2 | use hir::{Problem, source_binder}; | 4 | use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}}; |
3 | use ra_db::SourceDatabase; | 5 | use ra_db::SourceDatabase; |
4 | use ra_syntax::{ | 6 | use 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 | }; |
9 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use 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 | ||
36 | fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { | 57 | fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { |
@@ -71,12 +92,10 @@ fn check_unnecessary_braces_in_use_statement( | |||
71 | range, | 92 | range, |
72 | message: format!("Unnecessary braces in use statement"), | 93 | message: format!("Unnecessary braces in use statement"), |
73 | severity: Severity::WeakWarning, | 94 | severity: Severity::WeakWarning, |
74 | fix: Some(SourceChange { | 95 | fix: Some(SourceChange::source_file_edit( |
75 | label: "Remove unnecessary braces".to_string(), | 96 | "Remove unnecessary braces", |
76 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | 97 | SourceFileEdit { file_id, edit }, |
77 | file_system_edits: Vec::new(), | 98 | )), |
78 | cursor_position: None, | ||
79 | }), | ||
80 | }); | 99 | }); |
81 | } | 100 | } |
82 | 101 | ||
@@ -119,12 +138,10 @@ fn check_struct_shorthand_initialization( | |||
119 | range: named_field.syntax().range(), | 138 | range: named_field.syntax().range(), |
120 | message: format!("Shorthand struct initialization"), | 139 | message: format!("Shorthand struct initialization"), |
121 | severity: Severity::WeakWarning, | 140 | severity: Severity::WeakWarning, |
122 | fix: Some(SourceChange { | 141 | fix: Some(SourceChange::source_file_edit( |
123 | label: "use struct shorthand initialization".to_string(), | 142 | "use struct shorthand initialization", |
124 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | 143 | SourceFileEdit { file_id, edit }, |
125 | file_system_edits: Vec::new(), | 144 | )), |
126 | cursor_position: None, | ||
127 | }), | ||
128 | }); | 145 | }); |
129 | } | 146 | } |
130 | } | 147 | } |
@@ -132,39 +149,12 @@ fn check_struct_shorthand_initialization( | |||
132 | Some(()) | 149 | Some(()) |
133 | } | 150 | } |
134 | 151 | ||
135 | fn check_module( | ||
136 | acc: &mut Vec<Diagnostic>, | ||
137 | db: &RootDatabase, | ||
138 | file_id: FileId, | ||
139 | module: hir::Module, | ||
140 | ) { | ||
141 | let source_root = db.file_source_root(file_id); | ||
142 | for (name_node, problem) in module.problems(db) { | ||
143 | let diag = match problem { | ||
144 | Problem::UnresolvedModule { candidate } => { | ||
145 | let create_file = | ||
146 | FileSystemEdit::CreateFile { source_root, path: candidate.clone() }; | ||
147 | let fix = SourceChange { | ||
148 | label: "create module".to_string(), | ||
149 | source_file_edits: Vec::new(), | ||
150 | file_system_edits: vec![create_file], | ||
151 | cursor_position: None, | ||
152 | }; | ||
153 | Diagnostic { | ||
154 | range: name_node.range(), | ||
155 | message: "unresolved module".to_string(), | ||
156 | severity: Severity::Error, | ||
157 | fix: Some(fix), | ||
158 | } | ||
159 | } | ||
160 | }; | ||
161 | acc.push(diag) | ||
162 | } | ||
163 | } | ||
164 | |||
165 | #[cfg(test)] | 152 | #[cfg(test)] |
166 | mod tests { | 153 | mod tests { |
167 | 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; | ||
168 | 158 | ||
169 | use super::*; | 159 | use super::*; |
170 | 160 | ||
@@ -194,6 +184,34 @@ mod tests { | |||
194 | } | 184 | } |
195 | 185 | ||
196 | #[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] | ||
197 | fn test_check_unnecessary_braces_in_use_statement() { | 215 | fn test_check_unnecessary_braces_in_use_statement() { |
198 | check_not_applicable( | 216 | check_not_applicable( |
199 | " | 217 | " |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 99f18b6b8..9063f78a9 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -6,9 +6,6 @@ | |||
6 | //! database, and the `ra_hir` crate, where majority of the analysis happens. | 6 | //! database, and the `ra_hir` crate, where majority of the analysis happens. |
7 | //! However, IDE specific bits of the analysis (most notably completion) happen | 7 | //! However, IDE specific bits of the analysis (most notably completion) happen |
8 | //! in this crate. | 8 | //! in this crate. |
9 | //! | ||
10 | //! The sibling `ra_ide_api_light` handles those bits of IDE functionality | ||
11 | //! which are restricted to a single file and need only syntax. | ||
12 | 9 | ||
13 | // For proving that RootDatabase is RefUnwindSafe. | 10 | // For proving that RootDatabase is RefUnwindSafe. |
14 | #![recursion_limit = "128"] | 11 | #![recursion_limit = "128"] |
@@ -33,10 +30,11 @@ mod impls; | |||
33 | mod assists; | 30 | mod assists; |
34 | mod diagnostics; | 31 | mod diagnostics; |
35 | mod syntax_tree; | 32 | mod syntax_tree; |
36 | mod line_index; | ||
37 | mod folding_ranges; | 33 | mod folding_ranges; |
34 | mod line_index; | ||
38 | mod line_index_utils; | 35 | mod line_index_utils; |
39 | mod join_lines; | 36 | mod join_lines; |
37 | mod structure; | ||
40 | mod typing; | 38 | mod typing; |
41 | mod matching_brace; | 39 | mod matching_brace; |
42 | 40 | ||
@@ -72,9 +70,10 @@ pub use crate::{ | |||
72 | line_index_utils::translate_offset_with_edit, | 70 | line_index_utils::translate_offset_with_edit, |
73 | folding_ranges::{Fold, FoldKind}, | 71 | folding_ranges::{Fold, FoldKind}, |
74 | syntax_highlighting::HighlightedRange, | 72 | syntax_highlighting::HighlightedRange, |
73 | structure::{StructureNode, file_structure}, | ||
75 | diagnostics::Severity, | 74 | diagnostics::Severity, |
76 | }; | 75 | }; |
77 | pub use ra_ide_api_light::StructureNode; | 76 | |
78 | pub use ra_db::{ | 77 | pub use ra_db::{ |
79 | Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, | 78 | Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, |
80 | Edition | 79 | Edition |
@@ -97,6 +96,79 @@ pub struct SourceChange { | |||
97 | pub cursor_position: Option<FilePosition>, | 96 | pub cursor_position: Option<FilePosition>, |
98 | } | 97 | } |
99 | 98 | ||
99 | impl SourceChange { | ||
100 | /// Creates a new SourceChange with the given label | ||
101 | /// from the edits. | ||
102 | pub(crate) fn from_edits<L: Into<String>>( | ||
103 | label: L, | ||
104 | source_file_edits: Vec<SourceFileEdit>, | ||
105 | file_system_edits: Vec<FileSystemEdit>, | ||
106 | ) -> Self { | ||
107 | SourceChange { | ||
108 | label: label.into(), | ||
109 | source_file_edits, | ||
110 | file_system_edits, | ||
111 | cursor_position: None, | ||
112 | } | ||
113 | } | ||
114 | |||
115 | /// Creates a new SourceChange with the given label, | ||
116 | /// containing only the given `SourceFileEdits`. | ||
117 | pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { | ||
118 | SourceChange { | ||
119 | label: label.into(), | ||
120 | source_file_edits: edits, | ||
121 | file_system_edits: vec![], | ||
122 | cursor_position: None, | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /// Creates a new SourceChange with the given label, | ||
127 | /// containing only the given `FileSystemEdits`. | ||
128 | pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self { | ||
129 | SourceChange { | ||
130 | label: label.into(), | ||
131 | source_file_edits: vec![], | ||
132 | file_system_edits: edits, | ||
133 | cursor_position: None, | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /// Creates a new SourceChange with the given label, | ||
138 | /// containing only a single `SourceFileEdit`. | ||
139 | pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { | ||
140 | SourceChange::source_file_edits(label, vec![edit]) | ||
141 | } | ||
142 | |||
143 | /// Creates a new SourceChange with the given label | ||
144 | /// from the given `FileId` and `TextEdit` | ||
145 | pub(crate) fn source_file_edit_from<L: Into<String>>( | ||
146 | label: L, | ||
147 | file_id: FileId, | ||
148 | edit: TextEdit, | ||
149 | ) -> Self { | ||
150 | SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) | ||
151 | } | ||
152 | |||
153 | /// Creates a new SourceChange with the given label | ||
154 | /// from the given `FileId` and `TextEdit` | ||
155 | pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { | ||
156 | SourceChange::file_system_edits(label, vec![edit]) | ||
157 | } | ||
158 | |||
159 | /// Sets the cursor position to the given `FilePosition` | ||
160 | pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { | ||
161 | self.cursor_position = Some(cursor_position); | ||
162 | self | ||
163 | } | ||
164 | |||
165 | /// Sets the cursor position to the given `FilePosition` | ||
166 | pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { | ||
167 | self.cursor_position = cursor_position; | ||
168 | self | ||
169 | } | ||
170 | } | ||
171 | |||
100 | #[derive(Debug)] | 172 | #[derive(Debug)] |
101 | pub struct SourceFileEdit { | 173 | pub struct SourceFileEdit { |
102 | pub file_id: FileId, | 174 | pub file_id: FileId, |
@@ -285,12 +357,7 @@ impl Analysis { | |||
285 | file_id: frange.file_id, | 357 | file_id: frange.file_id, |
286 | edit: join_lines::join_lines(&file, frange.range), | 358 | edit: join_lines::join_lines(&file, frange.range), |
287 | }; | 359 | }; |
288 | SourceChange { | 360 | SourceChange::source_file_edit("join lines", file_edit) |
289 | label: "join lines".to_string(), | ||
290 | source_file_edits: vec![file_edit], | ||
291 | file_system_edits: vec![], | ||
292 | cursor_position: None, | ||
293 | } | ||
294 | } | 361 | } |
295 | 362 | ||
296 | /// Returns an edit which should be applied when opening a new line, fixing | 363 | /// Returns an edit which should be applied when opening a new line, fixing |
@@ -305,12 +372,10 @@ impl Analysis { | |||
305 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { | 372 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { |
306 | let file = self.db.parse(position.file_id); | 373 | let file = self.db.parse(position.file_id); |
307 | let edit = typing::on_eq_typed(&file, position.offset)?; | 374 | let edit = typing::on_eq_typed(&file, position.offset)?; |
308 | Some(SourceChange { | 375 | Some(SourceChange::source_file_edit( |
309 | label: "add semicolon".to_string(), | 376 | "add semicolon", |
310 | source_file_edits: vec![SourceFileEdit { edit, file_id: position.file_id }], | 377 | SourceFileEdit { edit, file_id: position.file_id }, |
311 | file_system_edits: vec![], | 378 | )) |
312 | cursor_position: None, | ||
313 | }) | ||
314 | } | 379 | } |
315 | 380 | ||
316 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. | 381 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. |
@@ -322,7 +387,7 @@ impl Analysis { | |||
322 | /// file outline. | 387 | /// file outline. |
323 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { | 388 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { |
324 | let file = self.db.parse(file_id); | 389 | let file = self.db.parse(file_id); |
325 | ra_ide_api_light::file_structure(&file) | 390 | structure::file_structure(&file) |
326 | } | 391 | } |
327 | 392 | ||
328 | /// Returns the set of folding ranges. | 393 | /// Returns the set of folding ranges. |
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs index b7784e577..22741445a 100644 --- a/crates/ra_ide_api/src/references.rs +++ b/crates/ra_ide_api/src/references.rs | |||
@@ -187,12 +187,7 @@ fn rename_mod( | |||
187 | }; | 187 | }; |
188 | source_file_edits.push(edit); | 188 | source_file_edits.push(edit); |
189 | 189 | ||
190 | Some(SourceChange { | 190 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) |
191 | label: "rename".to_string(), | ||
192 | source_file_edits, | ||
193 | file_system_edits, | ||
194 | cursor_position: None, | ||
195 | }) | ||
196 | } | 191 | } |
197 | 192 | ||
198 | fn rename_reference( | 193 | fn rename_reference( |
@@ -211,12 +206,7 @@ fn rename_reference( | |||
211 | return None; | 206 | return None; |
212 | } | 207 | } |
213 | 208 | ||
214 | Some(SourceChange { | 209 | Some(SourceChange::source_file_edits("rename", edit)) |
215 | label: "rename".to_string(), | ||
216 | source_file_edits: edit, | ||
217 | file_system_edits: Vec::new(), | ||
218 | cursor_position: None, | ||
219 | }) | ||
220 | } | 210 | } |
221 | 211 | ||
222 | #[cfg(test)] | 212 | #[cfg(test)] |
diff --git a/crates/ra_ide_api_light/src/snapshots/tests__file_structure.snap b/crates/ra_ide_api/src/snapshots/tests__file_structure.snap index 8e4184b31..2efa8e22c 100644 --- a/crates/ra_ide_api_light/src/snapshots/tests__file_structure.snap +++ b/crates/ra_ide_api/src/snapshots/tests__file_structure.snap | |||
@@ -1,7 +1,7 @@ | |||
1 | --- | 1 | --- |
2 | created: "2019-02-05T22:03:50.763530100Z" | 2 | created: "2019-02-05T22:03:50.763530100Z" |
3 | creator: [email protected] | 3 | creator: [email protected] |
4 | source: crates/ra_ide_api_light/src/structure.rs | 4 | source: crates/ra_ide_api/src/structure.rs |
5 | expression: structure | 5 | expression: structure |
6 | --- | 6 | --- |
7 | [ | 7 | [ |
diff --git a/crates/ra_ide_api_light/src/structure.rs b/crates/ra_ide_api/src/structure.rs index ec2c9bbc6..ec2c9bbc6 100644 --- a/crates/ra_ide_api_light/src/structure.rs +++ b/crates/ra_ide_api/src/structure.rs | |||
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 94b228466..501d44dbb 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs | |||
@@ -31,12 +31,14 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
31 | let cursor_position = position.offset + TextUnit::of_str(&inserted); | 31 | let cursor_position = position.offset + TextUnit::of_str(&inserted); |
32 | let mut edit = TextEditBuilder::default(); | 32 | let mut edit = TextEditBuilder::default(); |
33 | edit.insert(position.offset, inserted); | 33 | edit.insert(position.offset, inserted); |
34 | Some(SourceChange { | 34 | |
35 | label: "on enter".to_string(), | 35 | Some( |
36 | source_file_edits: vec![SourceFileEdit { edit: edit.finish(), file_id: position.file_id }], | 36 | SourceChange::source_file_edit( |
37 | file_system_edits: vec![], | 37 | "on enter", |
38 | cursor_position: Some(FilePosition { offset: cursor_position, file_id: position.file_id }), | 38 | SourceFileEdit { edit: edit.finish(), file_id: position.file_id }, |
39 | }) | 39 | ) |
40 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
41 | ) | ||
40 | } | 42 | } |
41 | 43 | ||
42 | fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { | 44 | fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { |
@@ -110,16 +112,14 @@ pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option< | |||
110 | TextRange::from_to(position.offset - current_indent_len, position.offset), | 112 | TextRange::from_to(position.offset - current_indent_len, position.offset), |
111 | target_indent.into(), | 113 | target_indent.into(), |
112 | ); | 114 | ); |
113 | let res = SourceChange { | 115 | |
114 | label: "reindent dot".to_string(), | 116 | let res = SourceChange::source_file_edit_from("reindent dot", position.file_id, edit.finish()) |
115 | source_file_edits: vec![SourceFileEdit { edit: edit.finish(), file_id: position.file_id }], | 117 | .with_cursor(FilePosition { |
116 | file_system_edits: vec![], | ||
117 | cursor_position: Some(FilePosition { | ||
118 | offset: position.offset + target_indent_len - current_indent_len | 118 | offset: position.offset + target_indent_len - current_indent_len |
119 | + TextUnit::of_char('.'), | 119 | + TextUnit::of_char('.'), |
120 | file_id: position.file_id, | 120 | file_id: position.file_id, |
121 | }), | 121 | }); |
122 | }; | 122 | |
123 | Some(res) | 123 | Some(res) |
124 | } | 124 | } |
125 | 125 | ||
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 @@ | |||
1 | use insta::assert_debug_snapshot_matches; | ||
2 | use ra_ide_api::{ | 1 | use 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::{ | |||
7 | use ra_syntax::SmolStr; | 6 | use ra_syntax::SmolStr; |
8 | 7 | ||
9 | #[test] | 8 | #[test] |
10 | fn 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] | ||
18 | fn 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] | ||
25 | fn test_resolve_crate_root() { | 9 | fn 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 | --- | ||
2 | created: "2019-01-22T14:45:01.486985900+00:00" | ||
3 | creator: [email protected] | ||
4 | expression: "&diagnostics" | ||
5 | source: "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_ide_api_light/Cargo.toml b/crates/ra_ide_api_light/Cargo.toml deleted file mode 100644 index 4e69f5325..000000000 --- a/crates/ra_ide_api_light/Cargo.toml +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | [package] | ||
2 | edition = "2018" | ||
3 | name = "ra_ide_api_light" | ||
4 | version = "0.1.0" | ||
5 | authors = ["rust-analyzer developers"] | ||
6 | publish = false | ||
7 | |||
8 | [dependencies] | ||
9 | itertools = "0.8.0" | ||
10 | superslice = "1.0.0" | ||
11 | join_to_string = "0.1.1" | ||
12 | rustc-hash = "1.0" | ||
13 | |||
14 | ra_syntax = { path = "../ra_syntax" } | ||
15 | ra_text_edit = { path = "../ra_text_edit" } | ||
16 | ra_fmt = { path = "../ra_fmt" } | ||
17 | |||
18 | [dev-dependencies] | ||
19 | test_utils = { path = "../test_utils" } | ||
20 | insta = "0.7.0" | ||
21 | |||
22 | [dev-dependencies.proptest] | ||
23 | version = "0.9.0" | ||
24 | # Disable `fork` feature to allow compiling on webassembly | ||
25 | default-features = false | ||
26 | features = ["std", "bit-set", "break-dead-code"] | ||
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs deleted file mode 100644 index df7f144b6..000000000 --- a/crates/ra_ide_api_light/src/lib.rs +++ /dev/null | |||
@@ -1,12 +0,0 @@ | |||
1 | //! This crate provides those IDE features which use only a single file. | ||
2 | //! | ||
3 | //! This usually means functions which take syntax tree as an input and produce | ||
4 | //! an edit or some auxiliary info. | ||
5 | |||
6 | mod structure; | ||
7 | |||
8 | use ra_syntax::TextRange; | ||
9 | |||
10 | pub use crate::{ | ||
11 | structure::{file_structure, StructureNode}, | ||
12 | }; | ||
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index d8c2cb063..a6fac07c4 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs | |||
@@ -521,7 +521,7 @@ pub enum PrefixOp { | |||
521 | } | 521 | } |
522 | 522 | ||
523 | impl PrefixExpr { | 523 | impl PrefixExpr { |
524 | pub fn op(&self) -> Option<PrefixOp> { | 524 | pub fn op_kind(&self) -> Option<PrefixOp> { |
525 | match self.syntax().first_child()?.kind() { | 525 | match self.syntax().first_child()?.kind() { |
526 | STAR => Some(PrefixOp::Deref), | 526 | STAR => Some(PrefixOp::Deref), |
527 | EXCL => Some(PrefixOp::Not), | 527 | EXCL => Some(PrefixOp::Not), |
@@ -529,6 +529,10 @@ impl PrefixExpr { | |||
529 | _ => None, | 529 | _ => None, |
530 | } | 530 | } |
531 | } | 531 | } |
532 | |||
533 | pub fn op(&self) -> Option<&SyntaxNode> { | ||
534 | self.syntax().first_child() | ||
535 | } | ||
532 | } | 536 | } |
533 | 537 | ||
534 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] | 538 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] |
@@ -598,44 +602,49 @@ pub enum BinOp { | |||
598 | } | 602 | } |
599 | 603 | ||
600 | impl BinExpr { | 604 | impl BinExpr { |
601 | pub fn op(&self) -> Option<BinOp> { | 605 | fn op_details(&self) -> Option<(&SyntaxNode, BinOp)> { |
602 | self.syntax() | 606 | self.syntax().children().find_map(|c| match c.kind() { |
603 | .children() | 607 | PIPEPIPE => Some((c, BinOp::BooleanOr)), |
604 | .filter_map(|c| match c.kind() { | 608 | AMPAMP => Some((c, BinOp::BooleanAnd)), |
605 | PIPEPIPE => Some(BinOp::BooleanOr), | 609 | EQEQ => Some((c, BinOp::EqualityTest)), |
606 | AMPAMP => Some(BinOp::BooleanAnd), | 610 | NEQ => Some((c, BinOp::NegatedEqualityTest)), |
607 | EQEQ => Some(BinOp::EqualityTest), | 611 | LTEQ => Some((c, BinOp::LesserEqualTest)), |
608 | NEQ => Some(BinOp::NegatedEqualityTest), | 612 | GTEQ => Some((c, BinOp::GreaterEqualTest)), |
609 | LTEQ => Some(BinOp::LesserEqualTest), | 613 | L_ANGLE => Some((c, BinOp::LesserTest)), |
610 | GTEQ => Some(BinOp::GreaterEqualTest), | 614 | R_ANGLE => Some((c, BinOp::GreaterTest)), |
611 | L_ANGLE => Some(BinOp::LesserTest), | 615 | PLUS => Some((c, BinOp::Addition)), |
612 | R_ANGLE => Some(BinOp::GreaterTest), | 616 | STAR => Some((c, BinOp::Multiplication)), |
613 | PLUS => Some(BinOp::Addition), | 617 | MINUS => Some((c, BinOp::Subtraction)), |
614 | STAR => Some(BinOp::Multiplication), | 618 | SLASH => Some((c, BinOp::Division)), |
615 | MINUS => Some(BinOp::Subtraction), | 619 | PERCENT => Some((c, BinOp::Remainder)), |
616 | SLASH => Some(BinOp::Division), | 620 | SHL => Some((c, BinOp::LeftShift)), |
617 | PERCENT => Some(BinOp::Remainder), | 621 | SHR => Some((c, BinOp::RightShift)), |
618 | SHL => Some(BinOp::LeftShift), | 622 | CARET => Some((c, BinOp::BitwiseXor)), |
619 | SHR => Some(BinOp::RightShift), | 623 | PIPE => Some((c, BinOp::BitwiseOr)), |
620 | CARET => Some(BinOp::BitwiseXor), | 624 | AMP => Some((c, BinOp::BitwiseAnd)), |
621 | PIPE => Some(BinOp::BitwiseOr), | 625 | DOTDOT => Some((c, BinOp::RangeRightOpen)), |
622 | AMP => Some(BinOp::BitwiseAnd), | 626 | DOTDOTEQ => Some((c, BinOp::RangeRightClosed)), |
623 | DOTDOT => Some(BinOp::RangeRightOpen), | 627 | EQ => Some((c, BinOp::Assignment)), |
624 | DOTDOTEQ => Some(BinOp::RangeRightClosed), | 628 | PLUSEQ => Some((c, BinOp::AddAssign)), |
625 | EQ => Some(BinOp::Assignment), | 629 | SLASHEQ => Some((c, BinOp::DivAssign)), |
626 | PLUSEQ => Some(BinOp::AddAssign), | 630 | STAREQ => Some((c, BinOp::MulAssign)), |
627 | SLASHEQ => Some(BinOp::DivAssign), | 631 | PERCENTEQ => Some((c, BinOp::RemAssign)), |
628 | STAREQ => Some(BinOp::MulAssign), | 632 | SHREQ => Some((c, BinOp::ShrAssign)), |
629 | PERCENTEQ => Some(BinOp::RemAssign), | 633 | SHLEQ => Some((c, BinOp::ShlAssign)), |
630 | SHREQ => Some(BinOp::ShrAssign), | 634 | MINUSEQ => Some((c, BinOp::SubAssign)), |
631 | SHLEQ => Some(BinOp::ShlAssign), | 635 | PIPEEQ => Some((c, BinOp::BitOrAssign)), |
632 | MINUSEQ => Some(BinOp::SubAssign), | 636 | AMPEQ => Some((c, BinOp::BitAndAssign)), |
633 | PIPEEQ => Some(BinOp::BitOrAssign), | 637 | CARETEQ => Some((c, BinOp::BitXorAssign)), |
634 | AMPEQ => Some(BinOp::BitAndAssign), | 638 | _ => None, |
635 | CARETEQ => Some(BinOp::BitXorAssign), | 639 | }) |
636 | _ => None, | 640 | } |
637 | }) | 641 | |
638 | .next() | 642 | pub fn op_kind(&self) -> Option<BinOp> { |
643 | self.op_details().map(|t| t.1) | ||
644 | } | ||
645 | |||
646 | pub fn op(&self) -> Option<&SyntaxNode> { | ||
647 | self.op_details().map(|t| t.0) | ||
639 | } | 648 | } |
640 | 649 | ||
641 | pub fn lhs(&self) -> Option<&Expr> { | 650 | pub fn lhs(&self) -> Option<&Expr> { |
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 | ||
67 | impl<N: AstNode> From<AstPtr<N>> for SyntaxNodePtr { | ||
68 | fn from(ptr: AstPtr<N>) -> SyntaxNodePtr { | ||
69 | ptr.raw | ||
70 | } | ||
71 | } | ||
72 | |||
67 | #[test] | 73 | #[test] |
68 | fn test_local_syntax_ptr() { | 74 | fn test_local_syntax_ptr() { |
69 | use crate::{ast, AstNode}; | 75 | use crate::{ast, AstNode}; |