aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/flip_eq_operands.rs86
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_cli/Cargo.toml1
-rw-r--r--crates/ra_cli/src/main.rs2
-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.rs19
-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/Cargo.toml1
-rw-r--r--crates/ra_ide_api/src/assists.rs11
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs112
-rw-r--r--crates/ra_ide_api/src/lib.rs101
-rw-r--r--crates/ra_ide_api/src/references.rs14
-rw-r--r--crates/ra_ide_api/src/snapshots/tests__file_structure.snap (renamed from crates/ra_ide_api_light/src/snapshots/tests__file_structure.snap)2
-rw-r--r--crates/ra_ide_api/src/structure.rs (renamed from crates/ra_ide_api_light/src/structure.rs)0
-rw-r--r--crates/ra_ide_api/src/typing.rs26
-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_ide_api_light/Cargo.toml26
-rw-r--r--crates/ra_ide_api_light/src/lib.rs12
-rw-r--r--crates/ra_syntax/src/ast.rs87
-rw-r--r--crates/ra_syntax/src/ptr.rs6
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 @@
1use hir::db::HirDatabase;
2use ra_syntax::ast::{AstNode, BinExpr, BinOp};
3
4use crate::{AssistCtx, Assist, AssistId};
5
6pub(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)]
27mod 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
88mod add_derive; 88mod add_derive;
89mod add_impl; 89mod add_impl;
90mod flip_comma; 90mod flip_comma;
91mod flip_eq_operands;
91mod change_visibility; 92mod change_visibility;
92mod fill_match_arms; 93mod fill_match_arms;
93mod fill_struct_fields; 94mod 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
15ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
16ra_ide_api = { path = "../ra_ide_api" } 16ra_ide_api = { path = "../ra_ide_api" }
17ra_ide_api_light = { path = "../ra_ide_api_light" }
18tools = { path = "../tools" } 17tools = { path = "../tools" }
19ra_batch = { path = "../ra_batch" } 18ra_batch = { path = "../ra_batch" }
20ra_hir = { path = "../ra_hir" } 19ra_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};
5use clap::{App, Arg, SubCommand}; 5use clap::{App, Arg, SubCommand};
6use join_to_string::join; 6use join_to_string::join;
7use ra_ide_api::{Analysis, FileRange}; 7use ra_ide_api::{Analysis, FileRange};
8use ra_ide_api_light::file_structure; 8use ra_ide_api::file_structure;
9use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; 9use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
10use tools::collect_tests; 10use tools::collect_tests;
11use flexi_logger::Logger; 11use 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 @@
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 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
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());
@@ -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;
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/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 }
20jemalloc-ctl = { version = "0.2.0", optional = true } 20jemalloc-ctl = { version = "0.2.0", optional = true }
21 21
22ra_syntax = { path = "../ra_syntax" } 22ra_syntax = { path = "../ra_syntax" }
23ra_ide_api_light = { path = "../ra_ide_api_light" }
24ra_text_edit = { path = "../ra_text_edit" } 23ra_text_edit = { path = "../ra_text_edit" }
25ra_db = { path = "../ra_db" } 24ra_db = { path = "../ra_db" }
26ra_fmt = { path = "../ra_fmt" } 25ra_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 @@
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) {
@@ -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
135fn 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)]
166mod tests { 153mod 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;
33mod assists; 30mod assists;
34mod diagnostics; 31mod diagnostics;
35mod syntax_tree; 32mod syntax_tree;
36mod line_index;
37mod folding_ranges; 33mod folding_ranges;
34mod line_index;
38mod line_index_utils; 35mod line_index_utils;
39mod join_lines; 36mod join_lines;
37mod structure;
40mod typing; 38mod typing;
41mod matching_brace; 39mod 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};
77pub use ra_ide_api_light::StructureNode; 76
78pub use ra_db::{ 77pub 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
99impl 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)]
101pub struct SourceFileEdit { 173pub 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
198fn rename_reference( 193fn 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---
2created: "2019-02-05T22:03:50.763530100Z" 2created: "2019-02-05T22:03:50.763530100Z"
3creator: [email protected] 3creator: [email protected]
4source: crates/ra_ide_api_light/src/structure.rs 4source: crates/ra_ide_api/src/structure.rs
5expression: structure 5expression: 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
42fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { 44fn 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 @@
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_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]
2edition = "2018"
3name = "ra_ide_api_light"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6publish = false
7
8[dependencies]
9itertools = "0.8.0"
10superslice = "1.0.0"
11join_to_string = "0.1.1"
12rustc-hash = "1.0"
13
14ra_syntax = { path = "../ra_syntax" }
15ra_text_edit = { path = "../ra_text_edit" }
16ra_fmt = { path = "../ra_fmt" }
17
18[dev-dependencies]
19test_utils = { path = "../test_utils" }
20insta = "0.7.0"
21
22[dev-dependencies.proptest]
23version = "0.9.0"
24# Disable `fork` feature to allow compiling on webassembly
25default-features = false
26features = ["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
6mod structure;
7
8use ra_syntax::TextRange;
9
10pub 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
523impl PrefixExpr { 523impl 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
600impl BinExpr { 604impl 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
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};