aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_analysis/src/db.rs2
-rw-r--r--crates/ra_analysis/src/imp.rs20
-rw-r--r--crates/ra_analysis/src/lib.rs3
-rw-r--r--crates/ra_editor/src/code_actions.rs74
-rw-r--r--crates/ra_editor/src/lib.rs2
-rw-r--r--crates/ra_hir/Cargo.toml3
-rw-r--r--crates/ra_hir/src/db.rs11
-rw-r--r--crates/ra_hir/src/function.rs18
-rw-r--r--crates/ra_hir/src/lib.rs20
-rw-r--r--crates/ra_hir/src/mock.rs13
-rw-r--r--crates/ra_hir/src/module.rs1
-rw-r--r--crates/ra_hir/src/module/nameres.rs4
-rw-r--r--crates/ra_hir/src/query_definitions.rs12
-rw-r--r--crates/ra_hir/src/ty.rs601
-rw-r--r--crates/ra_hir/src/ty/primitive.rs130
-rw-r--r--crates/ra_hir/src/ty/tests.rs134
-rw-r--r--crates/ra_hir/src/ty/tests/data/0001_basics.txt13
-rw-r--r--crates/ra_hir/src/ty/tests/data/0002_let.txt7
-rw-r--r--crates/ra_hir/src/ty/tests/data/0003_paths.txt9
-rw-r--r--crates/ra_lsp_server/src/main.rs11
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs8
-rw-r--r--crates/ra_syntax/src/ast/generated.rs60
-rw-r--r--crates/ra_syntax/src/grammar.ron19
-rw-r--r--crates/ra_syntax/tests/test.rs157
-rw-r--r--crates/test_utils/src/lib.rs99
-rw-r--r--editors/code/package.json11
-rw-r--r--editors/code/src/server.ts26
28 files changed, 1310 insertions, 159 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5bf946b34..51cf1825d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -695,6 +695,7 @@ name = "ra_hir"
695version = "0.1.0" 695version = "0.1.0"
696dependencies = [ 696dependencies = [
697 "arrayvec 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", 697 "arrayvec 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
698 "flexi_logger 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
698 "id-arena 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 699 "id-arena 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
699 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 700 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
700 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 701 "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs
index 94729d296..780a84291 100644
--- a/crates/ra_analysis/src/db.rs
+++ b/crates/ra_analysis/src/db.rs
@@ -93,6 +93,8 @@ salsa::database_storage! {
93 fn item_map() for hir::db::ItemMapQuery; 93 fn item_map() for hir::db::ItemMapQuery;
94 fn fn_syntax() for hir::db::FnSyntaxQuery; 94 fn fn_syntax() for hir::db::FnSyntaxQuery;
95 fn submodules() for hir::db::SubmodulesQuery; 95 fn submodules() for hir::db::SubmodulesQuery;
96 fn infer() for hir::db::InferQuery;
97 fn type_for_def() for hir::db::TypeForDefQuery;
96 } 98 }
97 } 99 }
98} 100}
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 00cdf7eff..a547c5a20 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -5,7 +5,8 @@ use std::{
5 5
6use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit, Severity}; 6use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit, Severity};
7use ra_syntax::{ 7use ra_syntax::{
8 ast::{self, ArgListOwner, Expr, NameOwner}, 8 ast::{self, ArgListOwner, Expr, NameOwner, FnDef},
9 algo::find_covering_node,
9 AstNode, SourceFileNode, 10 AstNode, SourceFileNode,
10 SyntaxKind::*, 11 SyntaxKind::*,
11 SyntaxNodeRef, TextRange, TextUnit, 12 SyntaxNodeRef, TextRange, TextUnit,
@@ -513,6 +514,23 @@ impl AnalysisImpl {
513 Ok(None) 514 Ok(None)
514 } 515 }
515 516
517 pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> {
518 let file = self.db.source_file(file_id);
519 let syntax = file.syntax();
520 let node = find_covering_node(syntax, range);
521 let parent_fn = node.ancestors().filter_map(FnDef::cast).next();
522 let parent_fn = if let Some(p) = parent_fn {
523 p
524 } else {
525 return Ok(None);
526 };
527 let function = ctry!(source_binder::function_from_source(
528 &*self.db, file_id, parent_fn
529 )?);
530 let infer = function.infer(&*self.db)?;
531 Ok(infer.type_of_node(node).map(|t| t.to_string()))
532 }
533
516 fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> { 534 fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> {
517 let name = name_ref.text(); 535 let name = name_ref.text();
518 let mut query = Query::new(name.to_string()); 536 let mut query = Query::new(name.to_string());
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index fbb19ed6b..a029f66b4 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -366,6 +366,9 @@ impl Analysis {
366 ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> { 366 ) -> Cancelable<Option<(FnSignatureInfo, Option<usize>)>> {
367 self.imp.resolve_callable(position) 367 self.imp.resolve_callable(position)
368 } 368 }
369 pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> {
370 self.imp.type_of(file_id, range)
371 }
369} 372}
370 373
371pub struct LibraryData { 374pub struct LibraryData {
diff --git a/crates/ra_editor/src/code_actions.rs b/crates/ra_editor/src/code_actions.rs
index 688a89c3d..1d78cb7e8 100644
--- a/crates/ra_editor/src/code_actions.rs
+++ b/crates/ra_editor/src/code_actions.rs
@@ -4,7 +4,7 @@ use ra_syntax::{
4 algo::{find_covering_node, find_leaf_at_offset}, 4 algo::{find_covering_node, find_leaf_at_offset},
5 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, 5 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
6 Direction, SourceFileNode, 6 Direction, SourceFileNode,
7 SyntaxKind::{COMMA, WHITESPACE, COMMENT}, 7 SyntaxKind::{COMMA, WHITESPACE, COMMENT, VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF},
8 SyntaxNodeRef, TextRange, TextUnit, 8 SyntaxNodeRef, TextRange, TextUnit,
9}; 9};
10 10
@@ -174,6 +174,39 @@ pub fn introduce_variable<'a>(
174 } 174 }
175} 175}
176 176
177pub fn make_pub_crate<'a>(
178 file: &'a SourceFileNode,
179 offset: TextUnit,
180) -> Option<impl FnOnce() -> LocalEdit + 'a> {
181 let syntax = file.syntax();
182
183 let keyword = find_leaf_at_offset(syntax, offset).find(|leaf| match leaf.kind() {
184 FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
185 _ => false,
186 })?;
187 let parent = keyword.parent()?;
188 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
189 let node_start = parent.range().start();
190 Some(move || {
191 let mut edit = TextEditBuilder::new();
192
193 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind())
194 || parent.children().any(|child| child.kind() == VISIBILITY)
195 {
196 return LocalEdit {
197 edit: edit.finish(),
198 cursor_position: Some(offset),
199 };
200 }
201
202 edit.insert(node_start, "pub(crate) ".to_string());
203 LocalEdit {
204 edit: edit.finish(),
205 cursor_position: Some(node_start),
206 }
207 })
208}
209
177fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> { 210fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option<SyntaxNodeRef> {
178 node.siblings(direction) 211 node.siblings(direction)
179 .skip(1) 212 .skip(1)
@@ -333,4 +366,43 @@ fn foo() {
333 ); 366 );
334 } 367 }
335 368
369 #[test]
370 fn test_make_pub_crate() {
371 check_action(
372 "<|>fn foo() {}",
373 "<|>pub(crate) fn foo() {}",
374 |file, off| make_pub_crate(file, off).map(|f| f()),
375 );
376 check_action(
377 "f<|>n foo() {}",
378 "<|>pub(crate) fn foo() {}",
379 |file, off| make_pub_crate(file, off).map(|f| f()),
380 );
381 check_action(
382 "<|>struct Foo {}",
383 "<|>pub(crate) struct Foo {}",
384 |file, off| make_pub_crate(file, off).map(|f| f()),
385 );
386 check_action("<|>mod foo {}", "<|>pub(crate) mod foo {}", |file, off| {
387 make_pub_crate(file, off).map(|f| f())
388 });
389 check_action(
390 "<|>trait Foo {}",
391 "<|>pub(crate) trait Foo {}",
392 |file, off| make_pub_crate(file, off).map(|f| f()),
393 );
394 check_action("m<|>od {}", "<|>pub(crate) mod {}", |file, off| {
395 make_pub_crate(file, off).map(|f| f())
396 });
397 check_action(
398 "pub(crate) f<|>n foo() {}",
399 "pub(crate) f<|>n foo() {}",
400 |file, off| make_pub_crate(file, off).map(|f| f()),
401 );
402 check_action(
403 "unsafe f<|>n foo() {}",
404 "<|>pub(crate) unsafe fn foo() {}",
405 |file, off| make_pub_crate(file, off).map(|f| f()),
406 );
407 }
336} 408}
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index 75e4a5f32..7a689b0f2 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -8,7 +8,7 @@ mod test_utils;
8mod typing; 8mod typing;
9 9
10pub use self::{ 10pub use self::{
11 code_actions::{add_derive, add_impl, flip_comma, introduce_variable, LocalEdit}, 11 code_actions::{add_derive, add_impl, flip_comma, introduce_variable, make_pub_crate, LocalEdit},
12 extend_selection::extend_selection, 12 extend_selection::extend_selection,
13 folding_ranges::{folding_ranges, Fold, FoldKind}, 13 folding_ranges::{folding_ranges, Fold, FoldKind},
14 line_index::{LineCol, LineIndex}, 14 line_index::{LineCol, LineIndex},
diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml
index 61650cee9..594176337 100644
--- a/crates/ra_hir/Cargo.toml
+++ b/crates/ra_hir/Cargo.toml
@@ -16,3 +16,6 @@ ra_syntax = { path = "../ra_syntax" }
16ra_editor = { path = "../ra_editor" } 16ra_editor = { path = "../ra_editor" }
17ra_db = { path = "../ra_db" } 17ra_db = { path = "../ra_db" }
18test_utils = { path = "../test_utils" } 18test_utils = { path = "../test_utils" }
19
20[dev-dependencies]
21flexi_logger = "0.10.0"
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs
index 62cf9ab17..d94f75857 100644
--- a/crates/ra_hir/src/db.rs
+++ b/crates/ra_hir/src/db.rs
@@ -14,6 +14,7 @@ use crate::{
14 function::FnId, 14 function::FnId,
15 module::{ModuleId, ModuleTree, ModuleSource, 15 module::{ModuleId, ModuleTree, ModuleSource,
16 nameres::{ItemMap, InputModuleItems}}, 16 nameres::{ItemMap, InputModuleItems}},
17 ty::{InferenceResult, Ty},
17}; 18};
18 19
19salsa::query_group! { 20salsa::query_group! {
@@ -30,6 +31,16 @@ pub trait HirDatabase: SyntaxDatabase
30 use fn query_definitions::fn_syntax; 31 use fn query_definitions::fn_syntax;
31 } 32 }
32 33
34 fn infer(fn_id: FnId) -> Cancelable<Arc<InferenceResult>> {
35 type InferQuery;
36 use fn query_definitions::infer;
37 }
38
39 fn type_for_def(def_id: DefId) -> Cancelable<Ty> {
40 type TypeForDefQuery;
41 use fn query_definitions::type_for_def;
42 }
43
33 fn file_items(file_id: FileId) -> Arc<SourceFileItems> { 44 fn file_items(file_id: FileId) -> Arc<SourceFileItems> {
34 type SourceFileItemsQuery; 45 type SourceFileItemsQuery;
35 use fn query_definitions::file_items; 46 use fn query_definitions::file_items;
diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs
index 2925beb16..d36477b48 100644
--- a/crates/ra_hir/src/function.rs
+++ b/crates/ra_hir/src/function.rs
@@ -5,12 +5,13 @@ use std::{
5 sync::Arc, 5 sync::Arc,
6}; 6};
7 7
8use ra_db::Cancelable;
8use ra_syntax::{ 9use ra_syntax::{
9 TextRange, TextUnit, 10 TextRange, TextUnit,
10 ast::{self, AstNode, DocCommentsOwner, NameOwner}, 11 ast::{self, AstNode, DocCommentsOwner, NameOwner},
11}; 12};
12 13
13use crate::{ DefId, HirDatabase }; 14use crate::{ DefId, HirDatabase, ty::InferenceResult, Module };
14 15
15pub use self::scope::FnScopes; 16pub use self::scope::FnScopes;
16 17
@@ -18,7 +19,7 @@ pub use self::scope::FnScopes;
18pub struct FnId(pub(crate) DefId); 19pub struct FnId(pub(crate) DefId);
19 20
20pub struct Function { 21pub struct Function {
21 fn_id: FnId, 22 pub(crate) fn_id: FnId,
22} 23}
23 24
24impl Function { 25impl Function {
@@ -27,6 +28,10 @@ impl Function {
27 Function { fn_id } 28 Function { fn_id }
28 } 29 }
29 30
31 pub fn syntax(&self, db: &impl HirDatabase) -> ast::FnDefNode {
32 db.fn_syntax(self.fn_id)
33 }
34
30 pub fn scopes(&self, db: &impl HirDatabase) -> Arc<FnScopes> { 35 pub fn scopes(&self, db: &impl HirDatabase) -> Arc<FnScopes> {
31 db.fn_scopes(self.fn_id) 36 db.fn_scopes(self.fn_id)
32 } 37 }
@@ -35,6 +40,15 @@ impl Function {
35 let syntax = db.fn_syntax(self.fn_id); 40 let syntax = db.fn_syntax(self.fn_id);
36 FnSignatureInfo::new(syntax.borrowed()) 41 FnSignatureInfo::new(syntax.borrowed())
37 } 42 }
43
44 pub fn infer(&self, db: &impl HirDatabase) -> Cancelable<Arc<InferenceResult>> {
45 db.infer(self.fn_id)
46 }
47
48 pub fn module(&self, db: &impl HirDatabase) -> Cancelable<Module> {
49 let loc = self.fn_id.0.loc(db);
50 Module::new(db, loc.source_root_id, loc.module_id)
51 }
38} 52}
39 53
40#[derive(Debug, Clone)] 54#[derive(Debug, Clone)]
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index f56214b47..a0d99a84d 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -25,10 +25,11 @@ pub mod source_binder;
25mod krate; 25mod krate;
26mod module; 26mod module;
27mod function; 27mod function;
28mod ty;
28 29
29use std::ops::Index; 30use std::ops::Index;
30 31
31use ra_syntax::{SyntaxNodeRef, SyntaxNode}; 32use ra_syntax::{SyntaxNodeRef, SyntaxNode, SyntaxKind};
32use ra_db::{LocationIntener, SourceRootId, FileId, Cancelable}; 33use ra_db::{LocationIntener, SourceRootId, FileId, Cancelable};
33 34
34use crate::{ 35use crate::{
@@ -66,6 +67,23 @@ pub struct DefLoc {
66 source_item_id: SourceItemId, 67 source_item_id: SourceItemId,
67} 68}
68 69
70impl DefKind {
71 pub(crate) fn for_syntax_kind(kind: SyntaxKind) -> Option<DefKind> {
72 match kind {
73 SyntaxKind::FN_DEF => Some(DefKind::Function),
74 SyntaxKind::MODULE => Some(DefKind::Module),
75 // These define items, but don't have their own DefKinds yet:
76 SyntaxKind::STRUCT_DEF => Some(DefKind::Item),
77 SyntaxKind::ENUM_DEF => Some(DefKind::Item),
78 SyntaxKind::TRAIT_DEF => Some(DefKind::Item),
79 SyntaxKind::TYPE_DEF => Some(DefKind::Item),
80 SyntaxKind::CONST_DEF => Some(DefKind::Item),
81 SyntaxKind::STATIC_DEF => Some(DefKind::Item),
82 _ => None,
83 }
84 }
85}
86
69impl DefId { 87impl DefId {
70 pub(crate) fn loc(self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefLoc { 88 pub(crate) fn loc(self, db: &impl AsRef<LocationIntener<DefLoc, DefId>>) -> DefLoc {
71 db.as_ref().id2loc(self) 89 db.as_ref().id2loc(self)
diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index 9423e6571..b5a997170 100644
--- a/crates/ra_hir/src/mock.rs
+++ b/crates/ra_hir/src/mock.rs
@@ -8,7 +8,7 @@ use test_utils::{parse_fixture, CURSOR_MARKER, extract_offset};
8 8
9use crate::{db, DefId, DefLoc}; 9use crate::{db, DefId, DefLoc};
10 10
11const WORKSPACE: SourceRootId = SourceRootId(0); 11pub const WORKSPACE: SourceRootId = SourceRootId(0);
12 12
13#[derive(Debug)] 13#[derive(Debug)]
14pub(crate) struct MockDatabase { 14pub(crate) struct MockDatabase {
@@ -24,6 +24,15 @@ impl MockDatabase {
24 (db, source_root) 24 (db, source_root)
25 } 25 }
26 26
27 pub(crate) fn with_single_file(text: &str) -> (MockDatabase, SourceRoot, FileId) {
28 let mut db = MockDatabase::default();
29 let mut source_root = SourceRoot::default();
30 let file_id = db.add_file(&mut source_root, "/main.rs", text);
31 db.query_mut(ra_db::SourceRootQuery)
32 .set(WORKSPACE, Arc::new(source_root.clone()));
33 (db, source_root, file_id)
34 }
35
27 pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) { 36 pub(crate) fn with_position(fixture: &str) -> (MockDatabase, FilePosition) {
28 let (db, _, position) = MockDatabase::from_fixture(fixture); 37 let (db, _, position) = MockDatabase::from_fixture(fixture);
29 let position = position.expect("expected a marker ( <|> )"); 38 let position = position.expect("expected a marker ( <|> )");
@@ -182,6 +191,8 @@ salsa::database_storage! {
182 fn item_map() for db::ItemMapQuery; 191 fn item_map() for db::ItemMapQuery;
183 fn fn_syntax() for db::FnSyntaxQuery; 192 fn fn_syntax() for db::FnSyntaxQuery;
184 fn submodules() for db::SubmodulesQuery; 193 fn submodules() for db::SubmodulesQuery;
194 fn infer() for db::InferQuery;
195 fn type_for_def() for db::TypeForDefQuery;
185 } 196 }
186 } 197 }
187} 198}
diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs
index cd31e8cfe..891119953 100644
--- a/crates/ra_hir/src/module.rs
+++ b/crates/ra_hir/src/module.rs
@@ -2,6 +2,7 @@ pub(super) mod imp;
2pub(super) mod nameres; 2pub(super) mod nameres;
3 3
4use std::sync::Arc; 4use std::sync::Arc;
5use log;
5 6
6use ra_syntax::{ 7use ra_syntax::{
7 algo::generate, 8 algo::generate,
diff --git a/crates/ra_hir/src/module/nameres.rs b/crates/ra_hir/src/module/nameres.rs
index 39e891cda..0b152a406 100644
--- a/crates/ra_hir/src/module/nameres.rs
+++ b/crates/ra_hir/src/module/nameres.rs
@@ -272,13 +272,13 @@ where
272 } 272 }
273 } 273 }
274 } 274 }
275 // Populate explicitelly declared items, except modules 275 // Populate explicitly declared items, except modules
276 for item in input.items.iter() { 276 for item in input.items.iter() {
277 if item.kind == MODULE { 277 if item.kind == MODULE {
278 continue; 278 continue;
279 } 279 }
280 let def_loc = DefLoc { 280 let def_loc = DefLoc {
281 kind: DefKind::Item, 281 kind: DefKind::for_syntax_kind(item.kind).unwrap_or(DefKind::Item),
282 source_root_id: self.source_root, 282 source_root_id: self.source_root,
283 module_id, 283 module_id,
284 source_item_id: SourceItemId { 284 source_item_id: SourceItemId {
diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs
index efaeb1525..b654af920 100644
--- a/crates/ra_hir/src/query_definitions.rs
+++ b/crates/ra_hir/src/query_definitions.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11use ra_db::{SourceRootId, FileId, Cancelable,}; 11use ra_db::{SourceRootId, FileId, Cancelable,};
12 12
13use crate::{ 13use crate::{
14 SourceFileItems, SourceItemId, DefKind, 14 SourceFileItems, SourceItemId, DefKind, Function, DefId,
15 db::HirDatabase, 15 db::HirDatabase,
16 function::{FnScopes, FnId}, 16 function::{FnScopes, FnId},
17 module::{ 17 module::{
@@ -19,6 +19,7 @@ use crate::{
19 imp::Submodule, 19 imp::Submodule,
20 nameres::{InputModuleItems, ItemMap, Resolver}, 20 nameres::{InputModuleItems, ItemMap, Resolver},
21 }, 21 },
22 ty::{self, InferenceResult, Ty}
22}; 23};
23 24
24/// Resolve `FnId` to the corresponding `SyntaxNode` 25/// Resolve `FnId` to the corresponding `SyntaxNode`
@@ -35,6 +36,15 @@ pub(super) fn fn_scopes(db: &impl HirDatabase, fn_id: FnId) -> Arc<FnScopes> {
35 Arc::new(res) 36 Arc::new(res)
36} 37}
37 38
39pub(super) fn infer(db: &impl HirDatabase, fn_id: FnId) -> Cancelable<Arc<InferenceResult>> {
40 let function = Function { fn_id };
41 ty::infer(db, function).map(Arc::new)
42}
43
44pub(super) fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Ty> {
45 ty::type_for_def(db, def_id)
46}
47
38pub(super) fn file_items(db: &impl HirDatabase, file_id: FileId) -> Arc<SourceFileItems> { 48pub(super) fn file_items(db: &impl HirDatabase, file_id: FileId) -> Arc<SourceFileItems> {
39 let mut res = SourceFileItems::new(file_id); 49 let mut res = SourceFileItems::new(file_id);
40 let source_file = db.source_file(file_id); 50 let source_file = db.source_file(file_id);
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
new file mode 100644
index 000000000..c759d4c8b
--- /dev/null
+++ b/crates/ra_hir/src/ty.rs
@@ -0,0 +1,601 @@
1mod primitive;
2#[cfg(test)]
3mod tests;
4
5use std::sync::Arc;
6use std::fmt;
7
8use log;
9use rustc_hash::{FxHashMap};
10
11use ra_db::{LocalSyntaxPtr, Cancelable};
12use ra_syntax::{
13 SmolStr,
14 ast::{self, AstNode, LoopBodyOwner, ArgListOwner},
15 SyntaxNodeRef
16};
17
18use crate::{Def, DefId, FnScopes, Module, Function, Path, db::HirDatabase};
19
20#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
21pub enum Ty {
22 /// The primitive boolean type. Written as `bool`.
23 Bool,
24
25 /// The primitive character type; holds a Unicode scalar value
26 /// (a non-surrogate code point). Written as `char`.
27 Char,
28
29 /// A primitive signed integer type. For example, `i32`.
30 Int(primitive::IntTy),
31
32 /// A primitive unsigned integer type. For example, `u32`.
33 Uint(primitive::UintTy),
34
35 /// A primitive floating-point type. For example, `f64`.
36 Float(primitive::FloatTy),
37
38 // Structures, enumerations and unions.
39 // Adt(AdtDef, Substs),
40 /// The pointee of a string slice. Written as `str`.
41 Str,
42
43 // An array with the given length. Written as `[T; n]`.
44 // Array(Ty, ty::Const),
45 /// The pointee of an array slice. Written as `[T]`.
46 Slice(TyRef),
47
48 // A raw pointer. Written as `*mut T` or `*const T`
49 // RawPtr(TypeAndMut<'tcx>),
50
51 // A reference; a pointer with an associated lifetime. Written as
52 // `&'a mut T` or `&'a T`.
53 // Ref(Ty<'tcx>, hir::Mutability),
54 /// A pointer to a function. Written as `fn() -> i32`.
55 ///
56 /// For example the type of `bar` here:
57 ///
58 /// ```rust
59 /// fn foo() -> i32 { 1 }
60 /// let bar: fn() -> i32 = foo;
61 /// ```
62 FnPtr(Arc<FnSig>),
63
64 // A trait, defined with `dyn trait`.
65 // Dynamic(),
66 /// The anonymous type of a closure. Used to represent the type of
67 /// `|a| a`.
68 // Closure(DefId, ClosureSubsts<'tcx>),
69
70 /// The anonymous type of a generator. Used to represent the type of
71 /// `|a| yield a`.
72 // Generator(DefId, GeneratorSubsts<'tcx>, hir::GeneratorMovability),
73
74 /// A type representin the types stored inside a generator.
75 /// This should only appear in GeneratorInteriors.
76 // GeneratorWitness(Binder<&'tcx List<Ty<'tcx>>>),
77
78 /// The never type `!`
79 Never,
80
81 /// A tuple type. For example, `(i32, bool)`.
82 Tuple(Vec<Ty>),
83
84 // The projection of an associated type. For example,
85 // `<T as Trait<..>>::N`.
86 // Projection(ProjectionTy),
87
88 // Opaque (`impl Trait`) type found in a return type.
89 // The `DefId` comes either from
90 // * the `impl Trait` ast::Ty node,
91 // * or the `existential type` declaration
92 // The substitutions are for the generics of the function in question.
93 // Opaque(DefId, Substs),
94
95 // A type parameter; for example, `T` in `fn f<T>(x: T) {}
96 // Param(ParamTy),
97
98 // A placeholder type - universally quantified higher-ranked type.
99 // Placeholder(ty::PlaceholderType),
100
101 // A type variable used during type checking.
102 // Infer(InferTy),
103 /// A placeholder for a type which could not be computed; this is
104 /// propagated to avoid useless error messages.
105 Unknown,
106}
107
108type TyRef = Arc<Ty>;
109
110#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
111pub struct FnSig {
112 input: Vec<Ty>,
113 output: Ty,
114}
115
116impl Ty {
117 pub fn new(_db: &impl HirDatabase, node: ast::TypeRef) -> Cancelable<Self> {
118 use ra_syntax::ast::TypeRef::*;
119 Ok(match node {
120 ParenType(_inner) => Ty::Unknown, // TODO
121 TupleType(_inner) => Ty::Unknown, // TODO
122 NeverType(..) => Ty::Never,
123 PathType(inner) => {
124 let path = if let Some(p) = inner.path() {
125 p
126 } else {
127 return Ok(Ty::Unknown);
128 };
129 if path.qualifier().is_none() {
130 let name = path
131 .segment()
132 .and_then(|s| s.name_ref())
133 .map(|n| n.text())
134 .unwrap_or(SmolStr::new(""));
135 if let Some(int_ty) = primitive::IntTy::from_string(&name) {
136 Ty::Int(int_ty)
137 } else if let Some(uint_ty) = primitive::UintTy::from_string(&name) {
138 Ty::Uint(uint_ty)
139 } else if let Some(float_ty) = primitive::FloatTy::from_string(&name) {
140 Ty::Float(float_ty)
141 } else {
142 // TODO
143 Ty::Unknown
144 }
145 } else {
146 // TODO
147 Ty::Unknown
148 }
149 }
150 PointerType(_inner) => Ty::Unknown, // TODO
151 ArrayType(_inner) => Ty::Unknown, // TODO
152 SliceType(_inner) => Ty::Unknown, // TODO
153 ReferenceType(_inner) => Ty::Unknown, // TODO
154 PlaceholderType(_inner) => Ty::Unknown, // TODO
155 FnPointerType(_inner) => Ty::Unknown, // TODO
156 ForType(_inner) => Ty::Unknown, // TODO
157 ImplTraitType(_inner) => Ty::Unknown, // TODO
158 DynTraitType(_inner) => Ty::Unknown, // TODO
159 })
160 }
161
162 pub fn unit() -> Self {
163 Ty::Tuple(Vec::new())
164 }
165}
166
167impl fmt::Display for Ty {
168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169 match self {
170 Ty::Bool => write!(f, "bool"),
171 Ty::Char => write!(f, "char"),
172 Ty::Int(t) => write!(f, "{}", t.ty_to_string()),
173 Ty::Uint(t) => write!(f, "{}", t.ty_to_string()),
174 Ty::Float(t) => write!(f, "{}", t.ty_to_string()),
175 Ty::Str => write!(f, "str"),
176 Ty::Slice(t) => write!(f, "[{}]", t),
177 Ty::Never => write!(f, "!"),
178 Ty::Tuple(ts) => {
179 write!(f, "(")?;
180 for t in ts {
181 write!(f, "{},", t)?;
182 }
183 write!(f, ")")
184 }
185 Ty::FnPtr(sig) => {
186 write!(f, "fn(")?;
187 for t in &sig.input {
188 write!(f, "{},", t)?;
189 }
190 write!(f, ") -> {}", sig.output)
191 }
192 Ty::Unknown => write!(f, "[unknown]"),
193 }
194 }
195}
196
197pub fn type_for_fn(db: &impl HirDatabase, f: Function) -> Cancelable<Ty> {
198 let syntax = f.syntax(db);
199 let node = syntax.borrowed();
200 // TODO we ignore type parameters for now
201 let input = node
202 .param_list()
203 .map(|pl| {
204 pl.params()
205 .map(|p| {
206 p.type_ref()
207 .map(|t| Ty::new(db, t))
208 .unwrap_or(Ok(Ty::Unknown))
209 })
210 .collect()
211 })
212 .unwrap_or_else(|| Ok(Vec::new()))?;
213 let output = node
214 .ret_type()
215 .and_then(|rt| rt.type_ref())
216 .map(|t| Ty::new(db, t))
217 .unwrap_or(Ok(Ty::Unknown))?;
218 let sig = FnSig { input, output };
219 Ok(Ty::FnPtr(Arc::new(sig)))
220}
221
222// TODO this should probably be per namespace (i.e. types vs. values), since for
223// a tuple struct `struct Foo(Bar)`, Foo has function type as a value, but
224// defines the struct type Foo when used in the type namespace. rustc has a
225// separate DefId for the constructor, but with the current DefId approach, that
226// seems complicated.
227pub fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable<Ty> {
228 let def = def_id.resolve(db)?;
229 match def {
230 Def::Module(..) => {
231 log::debug!("trying to get type for module {:?}", def_id);
232 Ok(Ty::Unknown)
233 }
234 Def::Function(f) => type_for_fn(db, f),
235 Def::Item => {
236 log::debug!("trying to get type for item of unknown type {:?}", def_id);
237 Ok(Ty::Unknown)
238 }
239 }
240}
241
242#[derive(Clone, PartialEq, Eq, Debug)]
243pub struct InferenceResult {
244 type_of: FxHashMap<LocalSyntaxPtr, Ty>,
245}
246
247impl InferenceResult {
248 pub fn type_of_node(&self, node: SyntaxNodeRef) -> Option<Ty> {
249 self.type_of.get(&LocalSyntaxPtr::new(node)).cloned()
250 }
251}
252
253#[derive(Clone, Debug)]
254pub struct InferenceContext<'a, D: HirDatabase> {
255 db: &'a D,
256 scopes: Arc<FnScopes>,
257 module: Module,
258 // TODO unification tables...
259 type_of: FxHashMap<LocalSyntaxPtr, Ty>,
260}
261
262impl<'a, D: HirDatabase> InferenceContext<'a, D> {
263 fn new(db: &'a D, scopes: Arc<FnScopes>, module: Module) -> Self {
264 InferenceContext {
265 type_of: FxHashMap::default(),
266 db,
267 scopes,
268 module,
269 }
270 }
271
272 fn write_ty(&mut self, node: SyntaxNodeRef, ty: Ty) {
273 self.type_of.insert(LocalSyntaxPtr::new(node), ty);
274 }
275
276 fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> Option<Ty> {
277 if *ty1 == Ty::Unknown {
278 return Some(ty2.clone());
279 }
280 if *ty2 == Ty::Unknown {
281 return Some(ty1.clone());
282 }
283 if ty1 == ty2 {
284 return Some(ty1.clone());
285 }
286 // TODO implement actual unification
287 return None;
288 }
289
290 fn unify_with_coercion(&mut self, ty1: &Ty, ty2: &Ty) -> Option<Ty> {
291 // TODO implement coercion
292 self.unify(ty1, ty2)
293 }
294
295 fn infer_path_expr(&mut self, expr: ast::PathExpr) -> Cancelable<Option<Ty>> {
296 let ast_path = ctry!(expr.path());
297 let path = ctry!(Path::from_ast(ast_path));
298 if path.is_ident() {
299 // resolve locally
300 let name = ctry!(ast_path.segment().and_then(|s| s.name_ref()));
301 if let Some(scope_entry) = self.scopes.resolve_local_name(name) {
302 let ty = ctry!(self.type_of.get(&scope_entry.ptr()));
303 return Ok(Some(ty.clone()));
304 };
305 };
306
307 // resolve in module
308 let resolved = ctry!(self.module.resolve_path(self.db, path)?);
309 let ty = self.db.type_for_def(resolved)?;
310 // TODO we will need to add type variables for type parameters etc. here
311 Ok(Some(ty))
312 }
313
314 fn infer_expr(&mut self, expr: ast::Expr) -> Cancelable<Ty> {
315 let ty = match expr {
316 ast::Expr::IfExpr(e) => {
317 if let Some(condition) = e.condition() {
318 if let Some(e) = condition.expr() {
319 // TODO if no pat, this should be bool
320 self.infer_expr(e)?;
321 }
322 // TODO write type for pat
323 };
324 let if_ty = if let Some(block) = e.then_branch() {
325 self.infer_block(block)?
326 } else {
327 Ty::Unknown
328 };
329 let else_ty = if let Some(block) = e.else_branch() {
330 self.infer_block(block)?
331 } else {
332 Ty::Unknown
333 };
334 if let Some(ty) = self.unify(&if_ty, &else_ty) {
335 ty
336 } else {
337 // TODO report diagnostic
338 Ty::Unknown
339 }
340 }
341 ast::Expr::BlockExpr(e) => {
342 if let Some(block) = e.block() {
343 self.infer_block(block)?
344 } else {
345 Ty::Unknown
346 }
347 }
348 ast::Expr::LoopExpr(e) => {
349 if let Some(block) = e.loop_body() {
350 self.infer_block(block)?;
351 };
352 // TODO never, or the type of the break param
353 Ty::Unknown
354 }
355 ast::Expr::WhileExpr(e) => {
356 if let Some(condition) = e.condition() {
357 if let Some(e) = condition.expr() {
358 // TODO if no pat, this should be bool
359 self.infer_expr(e)?;
360 }
361 // TODO write type for pat
362 };
363 if let Some(block) = e.loop_body() {
364 // TODO
365 self.infer_block(block)?;
366 };
367 // TODO always unit?
368 Ty::Unknown
369 }
370 ast::Expr::ForExpr(e) => {
371 if let Some(expr) = e.iterable() {
372 self.infer_expr(expr)?;
373 }
374 if let Some(_pat) = e.pat() {
375 // TODO write type for pat
376 }
377 if let Some(block) = e.loop_body() {
378 self.infer_block(block)?;
379 }
380 // TODO always unit?
381 Ty::Unknown
382 }
383 ast::Expr::LambdaExpr(e) => {
384 let _body_ty = if let Some(body) = e.body() {
385 self.infer_expr(body)?
386 } else {
387 Ty::Unknown
388 };
389 Ty::Unknown
390 }
391 ast::Expr::CallExpr(e) => {
392 let callee_ty = if let Some(e) = e.expr() {
393 self.infer_expr(e)?
394 } else {
395 Ty::Unknown
396 };
397 if let Some(arg_list) = e.arg_list() {
398 for arg in arg_list.args() {
399 // TODO unify / expect argument type
400 self.infer_expr(arg)?;
401 }
402 }
403 match callee_ty {
404 Ty::FnPtr(sig) => sig.output.clone(),
405 _ => {
406 // not callable
407 // TODO report an error?
408 Ty::Unknown
409 }
410 }
411 }
412 ast::Expr::MethodCallExpr(e) => {
413 let _receiver_ty = if let Some(e) = e.expr() {
414 self.infer_expr(e)?
415 } else {
416 Ty::Unknown
417 };
418 if let Some(arg_list) = e.arg_list() {
419 for arg in arg_list.args() {
420 // TODO unify / expect argument type
421 self.infer_expr(arg)?;
422 }
423 }
424 Ty::Unknown
425 }
426 ast::Expr::MatchExpr(e) => {
427 let _ty = if let Some(match_expr) = e.expr() {
428 self.infer_expr(match_expr)?
429 } else {
430 Ty::Unknown
431 };
432 if let Some(match_arm_list) = e.match_arm_list() {
433 for arm in match_arm_list.arms() {
434 // TODO type the bindings in pat
435 // TODO type the guard
436 let _ty = if let Some(e) = arm.expr() {
437 self.infer_expr(e)?
438 } else {
439 Ty::Unknown
440 };
441 }
442 // TODO unify all the match arm types
443 Ty::Unknown
444 } else {
445 Ty::Unknown
446 }
447 }
448 ast::Expr::TupleExpr(_e) => Ty::Unknown,
449 ast::Expr::ArrayExpr(_e) => Ty::Unknown,
450 ast::Expr::PathExpr(e) => self.infer_path_expr(e)?.unwrap_or(Ty::Unknown),
451 ast::Expr::ContinueExpr(_e) => Ty::Never,
452 ast::Expr::BreakExpr(_e) => Ty::Never,
453 ast::Expr::ParenExpr(e) => {
454 if let Some(e) = e.expr() {
455 self.infer_expr(e)?
456 } else {
457 Ty::Unknown
458 }
459 }
460 ast::Expr::Label(_e) => Ty::Unknown,
461 ast::Expr::ReturnExpr(e) => {
462 if let Some(e) = e.expr() {
463 // TODO unify with return type
464 self.infer_expr(e)?;
465 };
466 Ty::Never
467 }
468 ast::Expr::MatchArmList(_) | ast::Expr::MatchArm(_) | ast::Expr::MatchGuard(_) => {
469 // Can this even occur outside of a match expression?
470 Ty::Unknown
471 }
472 ast::Expr::StructLit(_e) => Ty::Unknown,
473 ast::Expr::NamedFieldList(_) | ast::Expr::NamedField(_) => {
474 // Can this even occur outside of a struct literal?
475 Ty::Unknown
476 }
477 ast::Expr::IndexExpr(_e) => Ty::Unknown,
478 ast::Expr::FieldExpr(_e) => Ty::Unknown,
479 ast::Expr::TryExpr(e) => {
480 let _inner_ty = if let Some(e) = e.expr() {
481 self.infer_expr(e)?
482 } else {
483 Ty::Unknown
484 };
485 Ty::Unknown
486 }
487 ast::Expr::CastExpr(e) => {
488 let _inner_ty = if let Some(e) = e.expr() {
489 self.infer_expr(e)?
490 } else {
491 Ty::Unknown
492 };
493 let cast_ty = e
494 .type_ref()
495 .map(|t| Ty::new(self.db, t))
496 .unwrap_or(Ok(Ty::Unknown))?;
497 // TODO do the coercion...
498 cast_ty
499 }
500 ast::Expr::RefExpr(e) => {
501 let _inner_ty = if let Some(e) = e.expr() {
502 self.infer_expr(e)?
503 } else {
504 Ty::Unknown
505 };
506 Ty::Unknown
507 }
508 ast::Expr::PrefixExpr(e) => {
509 let _inner_ty = if let Some(e) = e.expr() {
510 self.infer_expr(e)?
511 } else {
512 Ty::Unknown
513 };
514 Ty::Unknown
515 }
516 ast::Expr::RangeExpr(_e) => Ty::Unknown,
517 ast::Expr::BinExpr(_e) => Ty::Unknown,
518 ast::Expr::Literal(_e) => Ty::Unknown,
519 };
520 self.write_ty(expr.syntax(), ty.clone());
521 Ok(ty)
522 }
523
524 fn infer_block(&mut self, node: ast::Block) -> Cancelable<Ty> {
525 for stmt in node.statements() {
526 match stmt {
527 ast::Stmt::LetStmt(stmt) => {
528 let decl_ty = if let Some(type_ref) = stmt.type_ref() {
529 Ty::new(self.db, type_ref)?
530 } else {
531 Ty::Unknown
532 };
533 let ty = if let Some(expr) = stmt.initializer() {
534 // TODO pass expectation
535 let expr_ty = self.infer_expr(expr)?;
536 self.unify_with_coercion(&expr_ty, &decl_ty)
537 .unwrap_or(decl_ty)
538 } else {
539 decl_ty
540 };
541
542 if let Some(pat) = stmt.pat() {
543 self.write_ty(pat.syntax(), ty);
544 };
545 }
546 ast::Stmt::ExprStmt(expr_stmt) => {
547 if let Some(expr) = expr_stmt.expr() {
548 self.infer_expr(expr)?;
549 }
550 }
551 }
552 }
553 let ty = if let Some(expr) = node.expr() {
554 self.infer_expr(expr)?
555 } else {
556 Ty::unit()
557 };
558 self.write_ty(node.syntax(), ty.clone());
559 Ok(ty)
560 }
561}
562
563pub fn infer(db: &impl HirDatabase, function: Function) -> Cancelable<InferenceResult> {
564 let scopes = function.scopes(db);
565 let module = function.module(db)?;
566 let mut ctx = InferenceContext::new(db, scopes, module);
567
568 let syntax = function.syntax(db);
569 let node = syntax.borrowed();
570
571 if let Some(param_list) = node.param_list() {
572 for param in param_list.params() {
573 let pat = if let Some(pat) = param.pat() {
574 pat
575 } else {
576 continue;
577 };
578 if let Some(type_ref) = param.type_ref() {
579 let ty = Ty::new(db, type_ref)?;
580 ctx.type_of.insert(LocalSyntaxPtr::new(pat.syntax()), ty);
581 } else {
582 // TODO self param
583 ctx.type_of
584 .insert(LocalSyntaxPtr::new(pat.syntax()), Ty::Unknown);
585 };
586 }
587 }
588
589 // TODO get Ty for node.ret_type() and pass that to infer_block as expectation
590 // (see Expectation in rustc_typeck)
591
592 if let Some(block) = node.body() {
593 ctx.infer_block(block)?;
594 }
595
596 // TODO 'resolve' the types: replace inference variables by their inferred results
597
598 Ok(InferenceResult {
599 type_of: ctx.type_of,
600 })
601}
diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs
new file mode 100644
index 000000000..ad79b17e4
--- /dev/null
+++ b/crates/ra_hir/src/ty/primitive.rs
@@ -0,0 +1,130 @@
1use std::fmt;
2
3#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
4pub enum IntTy {
5 Isize,
6 I8,
7 I16,
8 I32,
9 I64,
10 I128,
11}
12
13impl fmt::Debug for IntTy {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 fmt::Display::fmt(self, f)
16 }
17}
18
19impl fmt::Display for IntTy {
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 write!(f, "{}", self.ty_to_string())
22 }
23}
24
25impl IntTy {
26 pub fn ty_to_string(&self) -> &'static str {
27 match *self {
28 IntTy::Isize => "isize",
29 IntTy::I8 => "i8",
30 IntTy::I16 => "i16",
31 IntTy::I32 => "i32",
32 IntTy::I64 => "i64",
33 IntTy::I128 => "i128",
34 }
35 }
36
37 pub fn from_string(s: &str) -> Option<IntTy> {
38 match s {
39 "isize" => Some(IntTy::Isize),
40 "i8" => Some(IntTy::I8),
41 "i16" => Some(IntTy::I16),
42 "i32" => Some(IntTy::I32),
43 "i64" => Some(IntTy::I64),
44 "i128" => Some(IntTy::I128),
45 _ => None,
46 }
47 }
48}
49
50#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
51pub enum UintTy {
52 Usize,
53 U8,
54 U16,
55 U32,
56 U64,
57 U128,
58}
59
60impl UintTy {
61 pub fn ty_to_string(&self) -> &'static str {
62 match *self {
63 UintTy::Usize => "usize",
64 UintTy::U8 => "u8",
65 UintTy::U16 => "u16",
66 UintTy::U32 => "u32",
67 UintTy::U64 => "u64",
68 UintTy::U128 => "u128",
69 }
70 }
71
72 pub fn from_string(s: &str) -> Option<UintTy> {
73 match s {
74 "usize" => Some(UintTy::Usize),
75 "u8" => Some(UintTy::U8),
76 "u16" => Some(UintTy::U16),
77 "u32" => Some(UintTy::U32),
78 "u64" => Some(UintTy::U64),
79 "u128" => Some(UintTy::U128),
80 _ => None,
81 }
82 }
83}
84
85impl fmt::Debug for UintTy {
86 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87 fmt::Display::fmt(self, f)
88 }
89}
90
91impl fmt::Display for UintTy {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 write!(f, "{}", self.ty_to_string())
94 }
95}
96
97#[derive(Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
98pub enum FloatTy {
99 F32,
100 F64,
101}
102
103impl fmt::Debug for FloatTy {
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 fmt::Display::fmt(self, f)
106 }
107}
108
109impl fmt::Display for FloatTy {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111 write!(f, "{}", self.ty_to_string())
112 }
113}
114
115impl FloatTy {
116 pub fn ty_to_string(self) -> &'static str {
117 match self {
118 FloatTy::F32 => "f32",
119 FloatTy::F64 => "f64",
120 }
121 }
122
123 pub fn from_string(s: &str) -> Option<FloatTy> {
124 match s {
125 "f32" => Some(FloatTy::F32),
126 "f64" => Some(FloatTy::F64),
127 _ => None,
128 }
129 }
130}
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
new file mode 100644
index 000000000..b6c02cd80
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -0,0 +1,134 @@
1use std::fmt::Write;
2use std::path::{PathBuf, Path};
3use std::fs;
4
5use ra_db::{SyntaxDatabase};
6use ra_syntax::ast::{self, AstNode};
7use test_utils::{project_dir, assert_eq_text, read_text};
8
9use crate::{
10 source_binder,
11 mock::MockDatabase,
12};
13
14// These tests compare the inference results for all expressions in a file
15// against snapshots of the current results. If you change something and these
16// tests fail expectedly, you can update the comparison files by deleting them
17// and running the tests again. Similarly, to add a new test, just write the
18// test here in the same pattern and it will automatically write the snapshot.
19
20#[test]
21fn infer_basics() {
22 check_inference(
23 r#"
24fn test(a: u32, b: isize, c: !, d: &str) {
25 a;
26 b;
27 c;
28 d;
29 1usize;
30 1isize;
31 "test";
32 1.0f32;
33}"#,
34 "0001_basics.txt",
35 );
36}
37
38#[test]
39fn infer_let() {
40 check_inference(
41 r#"
42fn test() {
43 let a = 1isize;
44 let b: usize = 1;
45 let c = b;
46}
47}"#,
48 "0002_let.txt",
49 );
50}
51
52#[test]
53fn infer_paths() {
54 check_inference(
55 r#"
56fn a() -> u32 { 1 }
57
58mod b {
59 fn c() -> u32 { 1 }
60}
61
62fn test() {
63 a();
64 b::c();
65}
66}"#,
67 "0003_paths.txt",
68 );
69}
70
71fn infer(content: &str) -> String {
72 let (db, _, file_id) = MockDatabase::with_single_file(content);
73 let source_file = db.source_file(file_id);
74 let mut acc = String::new();
75 for fn_def in source_file
76 .syntax()
77 .descendants()
78 .filter_map(ast::FnDef::cast)
79 {
80 let func = source_binder::function_from_source(&db, file_id, fn_def)
81 .unwrap()
82 .unwrap();
83 let inference_result = func.infer(&db).unwrap();
84 for (syntax_ptr, ty) in &inference_result.type_of {
85 let node = syntax_ptr.resolve(&source_file);
86 write!(
87 acc,
88 "{} '{}': {}\n",
89 syntax_ptr.range(),
90 ellipsize(node.text().to_string().replace("\n", " "), 15),
91 ty
92 )
93 .unwrap();
94 }
95 }
96 acc
97}
98
99fn check_inference(content: &str, data_file: impl AsRef<Path>) {
100 let data_file_path = test_data_dir().join(data_file);
101 let result = infer(content);
102
103 if !data_file_path.exists() {
104 println!("File with expected result doesn't exist, creating...\n");
105 println!("{}\n{}", content, result);
106 fs::write(&data_file_path, &result).unwrap();
107 panic!("File {:?} with expected result was created", data_file_path);
108 }
109
110 let expected = read_text(&data_file_path);
111 assert_eq_text!(&expected, &result);
112}
113
114fn ellipsize(mut text: String, max_len: usize) -> String {
115 if text.len() <= max_len {
116 return text;
117 }
118 let ellipsis = "...";
119 let e_len = ellipsis.len();
120 let mut prefix_len = (max_len - e_len) / 2;
121 while !text.is_char_boundary(prefix_len) {
122 prefix_len += 1;
123 }
124 let mut suffix_len = max_len - e_len - prefix_len;
125 while !text.is_char_boundary(text.len() - suffix_len) {
126 suffix_len += 1;
127 }
128 text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
129 text
130}
131
132fn test_data_dir() -> PathBuf {
133 project_dir().join("crates/ra_hir/src/ty/tests/data")
134}
diff --git a/crates/ra_hir/src/ty/tests/data/0001_basics.txt b/crates/ra_hir/src/ty/tests/data/0001_basics.txt
new file mode 100644
index 000000000..0c46f243a
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/0001_basics.txt
@@ -0,0 +1,13 @@
1[33; 34) 'd': [unknown]
2[88; 94) '1isize': [unknown]
3[48; 49) 'a': u32
4[55; 56) 'b': isize
5[112; 118) '1.0f32': [unknown]
6[76; 82) '1usize': [unknown]
7[9; 10) 'a': u32
8[27; 28) 'c': !
9[62; 63) 'c': !
10[17; 18) 'b': isize
11[100; 106) '"test"': [unknown]
12[42; 121) '{ ...f32; }': ()
13[69; 70) 'd': [unknown]
diff --git a/crates/ra_hir/src/ty/tests/data/0002_let.txt b/crates/ra_hir/src/ty/tests/data/0002_let.txt
new file mode 100644
index 000000000..2d0d1f57b
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/0002_let.txt
@@ -0,0 +1,7 @@
1[21; 22) 'a': [unknown]
2[52; 53) '1': [unknown]
3[11; 71) '{ ...= b; }': ()
4[63; 64) 'c': usize
5[25; 31) '1isize': [unknown]
6[41; 42) 'b': usize
7[67; 68) 'b': usize
diff --git a/crates/ra_hir/src/ty/tests/data/0003_paths.txt b/crates/ra_hir/src/ty/tests/data/0003_paths.txt
new file mode 100644
index 000000000..dcb5456ae
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/0003_paths.txt
@@ -0,0 +1,9 @@
1[15; 20) '{ 1 }': [unknown]
2[17; 18) '1': [unknown]
3[50; 51) '1': [unknown]
4[48; 53) '{ 1 }': [unknown]
5[82; 88) 'b::c()': u32
6[67; 91) '{ ...c(); }': ()
7[73; 74) 'a': fn() -> u32
8[73; 76) 'a()': u32
9[82; 86) 'b::c': fn() -> u32
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index eae601f91..33aa30d70 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -27,7 +27,10 @@ fn main() -> Result<()> {
27#[derive(Deserialize)] 27#[derive(Deserialize)]
28#[serde(rename_all = "camelCase")] 28#[serde(rename_all = "camelCase")]
29struct InitializationOptions { 29struct InitializationOptions {
30 publish_decorations: bool, 30 // Whether the client supports our custom highlighting publishing decorations.
31 // This is different to the highlightingOn setting, which is whether the user
32 // wants our custom highlighting to be used.
33 publish_decorations: Option<bool>,
31} 34}
32 35
33fn main_inner() -> Result<()> { 36fn main_inner() -> Result<()> {
@@ -42,12 +45,12 @@ fn main_inner() -> Result<()> {
42 .root_uri 45 .root_uri
43 .and_then(|it| it.to_file_path().ok()) 46 .and_then(|it| it.to_file_path().ok())
44 .unwrap_or(cwd); 47 .unwrap_or(cwd);
45 let publish_decorations = params 48 let supports_decorations = params
46 .initialization_options 49 .initialization_options
47 .and_then(|v| InitializationOptions::deserialize(v).ok()) 50 .and_then(|v| InitializationOptions::deserialize(v).ok())
48 .map(|it| it.publish_decorations) 51 .and_then(|it| it.publish_decorations)
49 == Some(true); 52 == Some(true);
50 ra_lsp_server::main_loop(false, root, publish_decorations, r, s) 53 ra_lsp_server::main_loop(false, root, supports_decorations, r, s)
51 }, 54 },
52 )?; 55 )?;
53 log::info!("shutting down IO..."); 56 log::info!("shutting down IO...");
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index c0c414db0..60f13267c 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -54,7 +54,7 @@ enum Task {
54pub fn main_loop( 54pub fn main_loop(
55 internal_mode: bool, 55 internal_mode: bool,
56 ws_root: PathBuf, 56 ws_root: PathBuf,
57 publish_decorations: bool, 57 supports_decorations: bool,
58 msg_receiver: &Receiver<RawMessage>, 58 msg_receiver: &Receiver<RawMessage>,
59 msg_sender: &Sender<RawMessage>, 59 msg_sender: &Sender<RawMessage>,
60) -> Result<()> { 60) -> Result<()> {
@@ -83,7 +83,7 @@ pub fn main_loop(
83 let mut subs = Subscriptions::new(); 83 let mut subs = Subscriptions::new();
84 let main_res = main_loop_inner( 84 let main_res = main_loop_inner(
85 internal_mode, 85 internal_mode,
86 publish_decorations, 86 supports_decorations,
87 &pool, 87 &pool,
88 msg_sender, 88 msg_sender,
89 msg_receiver, 89 msg_receiver,
@@ -156,7 +156,7 @@ impl fmt::Debug for Event {
156 156
157fn main_loop_inner( 157fn main_loop_inner(
158 internal_mode: bool, 158 internal_mode: bool,
159 publish_decorations: bool, 159 supports_decorations: bool,
160 pool: &ThreadPool, 160 pool: &ThreadPool,
161 msg_sender: &Sender<RawMessage>, 161 msg_sender: &Sender<RawMessage>,
162 msg_receiver: &Receiver<RawMessage>, 162 msg_receiver: &Receiver<RawMessage>,
@@ -240,7 +240,7 @@ fn main_loop_inner(
240 update_file_notifications_on_threadpool( 240 update_file_notifications_on_threadpool(
241 pool, 241 pool,
242 state.snapshot(), 242 state.snapshot(),
243 publish_decorations, 243 supports_decorations,
244 task_sender.clone(), 244 task_sender.clone(),
245 subs.subscriptions(), 245 subs.subscriptions(),
246 ) 246 )
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index bf056131e..c73533861 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -523,7 +523,15 @@ impl<R: TreeRoot<RaTypes>> CastExprNode<R> {
523} 523}
524 524
525 525
526impl<'a> CastExpr<'a> {} 526impl<'a> CastExpr<'a> {
527 pub fn expr(self) -> Option<Expr<'a>> {
528 super::child_opt(self)
529 }
530
531 pub fn type_ref(self) -> Option<TypeRef<'a>> {
532 super::child_opt(self)
533 }
534}
527 535
528// Char 536// Char
529#[derive(Debug, Clone, Copy,)] 537#[derive(Debug, Clone, Copy,)]
@@ -1553,6 +1561,10 @@ impl<'a> LetStmt<'a> {
1553 super::child_opt(self) 1561 super::child_opt(self)
1554 } 1562 }
1555 1563
1564 pub fn type_ref(self) -> Option<TypeRef<'a>> {
1565 super::child_opt(self)
1566 }
1567
1556 pub fn initializer(self) -> Option<Expr<'a>> { 1568 pub fn initializer(self) -> Option<Expr<'a>> {
1557 super::child_opt(self) 1569 super::child_opt(self)
1558 } 1570 }
@@ -2312,6 +2324,10 @@ impl<'a> Param<'a> {
2312 pub fn pat(self) -> Option<Pat<'a>> { 2324 pub fn pat(self) -> Option<Pat<'a>> {
2313 super::child_opt(self) 2325 super::child_opt(self)
2314 } 2326 }
2327
2328 pub fn type_ref(self) -> Option<TypeRef<'a>> {
2329 super::child_opt(self)
2330 }
2315} 2331}
2316 2332
2317// ParamList 2333// ParamList
@@ -2394,7 +2410,11 @@ impl<R: TreeRoot<RaTypes>> ParenExprNode<R> {
2394} 2410}
2395 2411
2396 2412
2397impl<'a> ParenExpr<'a> {} 2413impl<'a> ParenExpr<'a> {
2414 pub fn expr(self) -> Option<Expr<'a>> {
2415 super::child_opt(self)
2416 }
2417}
2398 2418
2399// ParenType 2419// ParenType
2400#[derive(Debug, Clone, Copy,)] 2420#[derive(Debug, Clone, Copy,)]
@@ -2681,7 +2701,11 @@ impl<R: TreeRoot<RaTypes>> PathTypeNode<R> {
2681} 2701}
2682 2702
2683 2703
2684impl<'a> PathType<'a> {} 2704impl<'a> PathType<'a> {
2705 pub fn path(self) -> Option<Path<'a>> {
2706 super::child_opt(self)
2707 }
2708}
2685 2709
2686// PlaceholderPat 2710// PlaceholderPat
2687#[derive(Debug, Clone, Copy,)] 2711#[derive(Debug, Clone, Copy,)]
@@ -2829,7 +2853,11 @@ impl<R: TreeRoot<RaTypes>> PrefixExprNode<R> {
2829} 2853}
2830 2854
2831 2855
2832impl<'a> PrefixExpr<'a> {} 2856impl<'a> PrefixExpr<'a> {
2857 pub fn expr(self) -> Option<Expr<'a>> {
2858 super::child_opt(self)
2859 }
2860}
2833 2861
2834// RangeExpr 2862// RangeExpr
2835#[derive(Debug, Clone, Copy,)] 2863#[derive(Debug, Clone, Copy,)]
@@ -2940,7 +2968,11 @@ impl<R: TreeRoot<RaTypes>> RefExprNode<R> {
2940} 2968}
2941 2969
2942 2970
2943impl<'a> RefExpr<'a> {} 2971impl<'a> RefExpr<'a> {
2972 pub fn expr(self) -> Option<Expr<'a>> {
2973 super::child_opt(self)
2974 }
2975}
2944 2976
2945// RefPat 2977// RefPat
2946#[derive(Debug, Clone, Copy,)] 2978#[derive(Debug, Clone, Copy,)]
@@ -3051,7 +3083,11 @@ impl<R: TreeRoot<RaTypes>> RetTypeNode<R> {
3051} 3083}
3052 3084
3053 3085
3054impl<'a> RetType<'a> {} 3086impl<'a> RetType<'a> {
3087 pub fn type_ref(self) -> Option<TypeRef<'a>> {
3088 super::child_opt(self)
3089 }
3090}
3055 3091
3056// ReturnExpr 3092// ReturnExpr
3057#[derive(Debug, Clone, Copy,)] 3093#[derive(Debug, Clone, Copy,)]
@@ -3088,7 +3124,11 @@ impl<R: TreeRoot<RaTypes>> ReturnExprNode<R> {
3088} 3124}
3089 3125
3090 3126
3091impl<'a> ReturnExpr<'a> {} 3127impl<'a> ReturnExpr<'a> {
3128 pub fn expr(self) -> Option<Expr<'a>> {
3129 super::child_opt(self)
3130 }
3131}
3092 3132
3093// SelfParam 3133// SelfParam
3094#[derive(Debug, Clone, Copy,)] 3134#[derive(Debug, Clone, Copy,)]
@@ -3578,7 +3618,11 @@ impl<R: TreeRoot<RaTypes>> TryExprNode<R> {
3578} 3618}
3579 3619
3580 3620
3581impl<'a> TryExpr<'a> {} 3621impl<'a> TryExpr<'a> {
3622 pub fn expr(self) -> Option<Expr<'a>> {
3623 super::child_opt(self)
3624 }
3625}
3582 3626
3583// TupleExpr 3627// TupleExpr
3584#[derive(Debug, Clone, Copy,)] 3628#[derive(Debug, Clone, Copy,)]
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index eed67637e..e3b9032a0 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -254,7 +254,7 @@ Grammar(
254 ], 254 ],
255 options: [ "ParamList", ["body", "Block"], "RetType" ], 255 options: [ "ParamList", ["body", "Block"], "RetType" ],
256 ), 256 ),
257 "RetType": (), 257 "RetType": (options: ["TypeRef"]),
258 "StructDef": ( 258 "StructDef": (
259 traits: [ 259 traits: [
260 "NameOwner", 260 "NameOwner",
@@ -304,7 +304,7 @@ Grammar(
304 "ParenType": (), 304 "ParenType": (),
305 "TupleType": (), 305 "TupleType": (),
306 "NeverType": (), 306 "NeverType": (),
307 "PathType": (), 307 "PathType": (options: ["Path"]),
308 "PointerType": (), 308 "PointerType": (),
309 "ArrayType": (), 309 "ArrayType": (),
310 "SliceType": (), 310 "SliceType": (),
@@ -346,7 +346,7 @@ Grammar(
346 346
347 "TupleExpr": (), 347 "TupleExpr": (),
348 "ArrayExpr": (), 348 "ArrayExpr": (),
349 "ParenExpr": (), 349 "ParenExpr": (options: ["Expr"]),
350 "PathExpr": (options: ["Path"]), 350 "PathExpr": (options: ["Path"]),
351 "LambdaExpr": ( 351 "LambdaExpr": (
352 options: [ 352 options: [
@@ -377,7 +377,7 @@ Grammar(
377 "BlockExpr": ( 377 "BlockExpr": (
378 options: [ "Block" ] 378 options: [ "Block" ]
379 ), 379 ),
380 "ReturnExpr": (), 380 "ReturnExpr": (options: ["Expr"]),
381 "MatchExpr": ( 381 "MatchExpr": (
382 options: [ "Expr", "MatchArmList" ], 382 options: [ "Expr", "MatchArmList" ],
383 ), 383 ),
@@ -405,10 +405,10 @@ Grammar(
405 ), 405 ),
406 "IndexExpr": (), 406 "IndexExpr": (),
407 "FieldExpr": (), 407 "FieldExpr": (),
408 "TryExpr": (), 408 "TryExpr": (options: ["Expr"]),
409 "CastExpr": (), 409 "CastExpr": (options: ["Expr", "TypeRef"]),
410 "RefExpr": (), 410 "RefExpr": (options: ["Expr"]),
411 "PrefixExpr": (), 411 "PrefixExpr": (options: ["Expr"]),
412 "RangeExpr": (), 412 "RangeExpr": (),
413 "BinExpr": (), 413 "BinExpr": (),
414 "String": (), 414 "String": (),
@@ -499,6 +499,7 @@ Grammar(
499 ), 499 ),
500 "LetStmt": ( options: [ 500 "LetStmt": ( options: [
501 ["pat", "Pat"], 501 ["pat", "Pat"],
502 ["type_ref", "TypeRef"],
502 ["initializer", "Expr"], 503 ["initializer", "Expr"],
503 ]), 504 ]),
504 "Condition": ( 505 "Condition": (
@@ -521,7 +522,7 @@ Grammar(
521 ), 522 ),
522 "SelfParam": (), 523 "SelfParam": (),
523 "Param": ( 524 "Param": (
524 options: [ "Pat" ], 525 options: [ "Pat", "TypeRef" ],
525 ), 526 ),
526 "UseItem": ( 527 "UseItem": (
527 options: [ "UseTree" ] 528 options: [ "UseTree" ]
diff --git a/crates/ra_syntax/tests/test.rs b/crates/ra_syntax/tests/test.rs
index 4266864bd..2235dc401 100644
--- a/crates/ra_syntax/tests/test.rs
+++ b/crates/ra_syntax/tests/test.rs
@@ -1,14 +1,13 @@
1extern crate ra_syntax; 1extern crate ra_syntax;
2#[macro_use]
3extern crate test_utils; 2extern crate test_utils;
4extern crate walkdir; 3extern crate walkdir;
5 4
6use std::{ 5use std::{
7 fmt::Write, 6 fmt::Write,
8 fs, 7 path::{PathBuf, Component},
9 path::{Path, PathBuf, Component},
10}; 8};
11 9
10use test_utils::{project_dir, dir_tests, read_text, collect_tests};
12use ra_syntax::{ 11use ra_syntax::{
13 utils::{check_fuzz_invariants, dump_tree}, 12 utils::{check_fuzz_invariants, dump_tree},
14 SourceFileNode, 13 SourceFileNode,
@@ -16,7 +15,7 @@ use ra_syntax::{
16 15
17#[test] 16#[test]
18fn lexer_tests() { 17fn lexer_tests() {
19 dir_tests(&["lexer"], |text, _| { 18 dir_tests(&test_data_dir(), &["lexer"], |text, _| {
20 let tokens = ra_syntax::tokenize(text); 19 let tokens = ra_syntax::tokenize(text);
21 dump_tokens(&tokens, text) 20 dump_tokens(&tokens, text)
22 }) 21 })
@@ -24,33 +23,41 @@ fn lexer_tests() {
24 23
25#[test] 24#[test]
26fn parser_tests() { 25fn parser_tests() {
27 dir_tests(&["parser/inline/ok", "parser/ok"], |text, path| { 26 dir_tests(
28 let file = SourceFileNode::parse(text); 27 &test_data_dir(),
29 let errors = file.errors(); 28 &["parser/inline/ok", "parser/ok"],
30 assert_eq!( 29 |text, path| {
31 &*errors, 30 let file = SourceFileNode::parse(text);
32 &[] as &[ra_syntax::SyntaxError], 31 let errors = file.errors();
33 "There should be no errors in the file {:?}", 32 assert_eq!(
34 path.display() 33 &*errors,
35 ); 34 &[] as &[ra_syntax::SyntaxError],
36 dump_tree(file.syntax()) 35 "There should be no errors in the file {:?}",
37 }); 36 path.display()
38 dir_tests(&["parser/err", "parser/inline/err"], |text, path| { 37 );
39 let file = SourceFileNode::parse(text); 38 dump_tree(file.syntax())
40 let errors = file.errors(); 39 },
41 assert_ne!( 40 );
42 &*errors, 41 dir_tests(
43 &[] as &[ra_syntax::SyntaxError], 42 &test_data_dir(),
44 "There should be errors in the file {:?}", 43 &["parser/err", "parser/inline/err"],
45 path.display() 44 |text, path| {
46 ); 45 let file = SourceFileNode::parse(text);
47 dump_tree(file.syntax()) 46 let errors = file.errors();
48 }); 47 assert_ne!(
48 &*errors,
49 &[] as &[ra_syntax::SyntaxError],
50 "There should be errors in the file {:?}",
51 path.display()
52 );
53 dump_tree(file.syntax())
54 },
55 );
49} 56}
50 57
51#[test] 58#[test]
52fn parser_fuzz_tests() { 59fn parser_fuzz_tests() {
53 for (_, text) in collect_tests(&["parser/fuzz-failures"]) { 60 for (_, text) in collect_tests(&test_data_dir(), &["parser/fuzz-failures"]) {
54 check_fuzz_invariants(&text) 61 check_fuzz_invariants(&text)
55 } 62 }
56} 63}
@@ -92,102 +99,6 @@ fn self_hosting_parsing() {
92 "self_hosting_parsing found too few files - is it running in the right directory?" 99 "self_hosting_parsing found too few files - is it running in the right directory?"
93 ) 100 )
94} 101}
95/// Read file and normalize newlines.
96///
97/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
98///
99/// ```
100/// let s = "
101/// ";
102/// assert_eq!(s.as_bytes(), &[10]);
103/// ```
104///
105/// so this should always be correct.
106fn read_text(path: &Path) -> String {
107 fs::read_to_string(path)
108 .expect(&format!("File at {:?} should be valid", path))
109 .replace("\r\n", "\n")
110}
111
112fn dir_tests<F>(paths: &[&str], f: F)
113where
114 F: Fn(&str, &Path) -> String,
115{
116 for (path, input_code) in collect_tests(paths) {
117 let parse_tree = f(&input_code, &path);
118 let path = path.with_extension("txt");
119 if !path.exists() {
120 println!("\nfile: {}", path.display());
121 println!("No .txt file with expected result, creating...\n");
122 println!("{}\n{}", input_code, parse_tree);
123 fs::write(&path, &parse_tree).unwrap();
124 panic!("No expected result")
125 }
126 let expected = read_text(&path);
127 let expected = expected.as_str();
128 let parse_tree = parse_tree.as_str();
129 assert_equal_text(expected, parse_tree, &path);
130 }
131}
132
133const REWRITE: bool = false;
134
135fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
136 if expected == actual {
137 return;
138 }
139 let dir = project_dir();
140 let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
141 if expected.trim() == actual.trim() {
142 println!("whitespace difference, rewriting");
143 println!("file: {}\n", pretty_path.display());
144 fs::write(path, actual).unwrap();
145 return;
146 }
147 if REWRITE {
148 println!("rewriting {}", pretty_path.display());
149 fs::write(path, actual).unwrap();
150 return;
151 }
152 assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
153}
154
155fn collect_tests(paths: &[&str]) -> Vec<(PathBuf, String)> {
156 paths
157 .iter()
158 .flat_map(|path| {
159 let path = test_data_dir().join(path);
160 test_from_dir(&path).into_iter()
161 })
162 .map(|path| {
163 let text = read_text(&path);
164 (path, text)
165 })
166 .collect()
167}
168
169fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
170 let mut acc = Vec::new();
171 for file in fs::read_dir(&dir).unwrap() {
172 let file = file.unwrap();
173 let path = file.path();
174 if path.extension().unwrap_or_default() == "rs" {
175 acc.push(path);
176 }
177 }
178 acc.sort();
179 acc
180}
181
182fn project_dir() -> PathBuf {
183 let dir = env!("CARGO_MANIFEST_DIR");
184 PathBuf::from(dir)
185 .parent()
186 .unwrap()
187 .parent()
188 .unwrap()
189 .to_owned()
190}
191 102
192fn test_data_dir() -> PathBuf { 103fn test_data_dir() -> PathBuf {
193 project_dir().join("crates/ra_syntax/tests/data") 104 project_dir().join("crates/ra_syntax/tests/data")
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index beb936c61..012b1d0b4 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -1,4 +1,6 @@
1use std::fmt; 1use std::fmt;
2use std::fs;
3use std::path::{Path, PathBuf};
2 4
3use itertools::Itertools; 5use itertools::Itertools;
4use text_unit::{TextRange, TextUnit}; 6use text_unit::{TextRange, TextUnit};
@@ -262,3 +264,100 @@ pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a
262 _ => Some((expected, actual)), 264 _ => Some((expected, actual)),
263 } 265 }
264} 266}
267
268pub fn dir_tests<F>(test_data_dir: &Path, paths: &[&str], f: F)
269where
270 F: Fn(&str, &Path) -> String,
271{
272 for (path, input_code) in collect_tests(test_data_dir, paths) {
273 let parse_tree = f(&input_code, &path);
274 let path = path.with_extension("txt");
275 if !path.exists() {
276 println!("\nfile: {}", path.display());
277 println!("No .txt file with expected result, creating...\n");
278 println!("{}\n{}", input_code, parse_tree);
279 fs::write(&path, &parse_tree).unwrap();
280 panic!("No expected result")
281 }
282 let expected = read_text(&path);
283 let expected = expected.as_str();
284 let parse_tree = parse_tree.as_str();
285 assert_equal_text(expected, parse_tree, &path);
286 }
287}
288
289pub fn collect_tests(test_data_dir: &Path, paths: &[&str]) -> Vec<(PathBuf, String)> {
290 paths
291 .iter()
292 .flat_map(|path| {
293 let path = test_data_dir.to_owned().join(path);
294 test_from_dir(&path).into_iter()
295 })
296 .map(|path| {
297 let text = read_text(&path);
298 (path, text)
299 })
300 .collect()
301}
302
303fn test_from_dir(dir: &Path) -> Vec<PathBuf> {
304 let mut acc = Vec::new();
305 for file in fs::read_dir(&dir).unwrap() {
306 let file = file.unwrap();
307 let path = file.path();
308 if path.extension().unwrap_or_default() == "rs" {
309 acc.push(path);
310 }
311 }
312 acc.sort();
313 acc
314}
315
316pub fn project_dir() -> PathBuf {
317 let dir = env!("CARGO_MANIFEST_DIR");
318 PathBuf::from(dir)
319 .parent()
320 .unwrap()
321 .parent()
322 .unwrap()
323 .to_owned()
324}
325
326/// Read file and normalize newlines.
327///
328/// `rustc` seems to always normalize `\r\n` newlines to `\n`:
329///
330/// ```
331/// let s = "
332/// ";
333/// assert_eq!(s.as_bytes(), &[10]);
334/// ```
335///
336/// so this should always be correct.
337pub fn read_text(path: &Path) -> String {
338 fs::read_to_string(path)
339 .expect(&format!("File at {:?} should be valid", path))
340 .replace("\r\n", "\n")
341}
342
343const REWRITE: bool = false;
344
345fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
346 if expected == actual {
347 return;
348 }
349 let dir = project_dir();
350 let pretty_path = path.strip_prefix(&dir).unwrap_or_else(|_| path);
351 if expected.trim() == actual.trim() {
352 println!("whitespace difference, rewriting");
353 println!("file: {}\n", pretty_path.display());
354 fs::write(path, actual).unwrap();
355 return;
356 }
357 if REWRITE {
358 println!("rewriting {}", pretty_path.display());
359 fs::write(path, actual).unwrap();
360 return;
361 }
362 assert_eq_text!(expected, actual, "file: {}", pretty_path.display());
363}
diff --git a/editors/code/package.json b/editors/code/package.json
index d53e44b21..2989a7016 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -131,6 +131,17 @@
131 "type": "boolean", 131 "type": "boolean",
132 "default": true, 132 "default": true,
133 "description": "Highlight Rust code (overrides built-in syntax highlighting)" 133 "description": "Highlight Rust code (overrides built-in syntax highlighting)"
134 },
135 "ra-lsp.trace.server": {
136 "type": "string",
137 "scope": "window",
138 "enum": [
139 "off",
140 "messages",
141 "verbose"
142 ],
143 "default": "off",
144 "description": "Trace requests to the ra-lsp server"
134 } 145 }
135 } 146 }
136 }, 147 },
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
index 75e273f37..75bdf3207 100644
--- a/editors/code/src/server.ts
+++ b/editors/code/src/server.ts
@@ -22,7 +22,7 @@ export class Server {
22 const clientOptions: lc.LanguageClientOptions = { 22 const clientOptions: lc.LanguageClientOptions = {
23 documentSelector: [{ scheme: 'file', language: 'rust' }], 23 documentSelector: [{ scheme: 'file', language: 'rust' }],
24 initializationOptions: { 24 initializationOptions: {
25 publishDecorations: true, 25 publishDecorations: true
26 } 26 }
27 }; 27 };
28 28
@@ -32,6 +32,30 @@ export class Server {
32 serverOptions, 32 serverOptions,
33 clientOptions 33 clientOptions
34 ); 34 );
35 // HACK: This is an awful way of filtering out the decorations notifications
36 // However, pending proper support, this is the most effecitve approach
37 // Proper support for this would entail a change to vscode-languageclient to allow not notifying on certain messages
38 // Or the ability to disable the serverside component of highlighting (but this means that to do tracing we need to disable hihlighting)
39 // This also requires considering our settings strategy, which is work which needs doing
40 // @ts-ignore The tracer is private to vscode-languageclient, but we need access to it to not log publishDecorations requests
41 Server.client._tracer = {
42 log: (messageOrDataObject: string | any, data?: string) => {
43 if (typeof messageOrDataObject === 'string') {
44 if (
45 messageOrDataObject.includes('m/publishDecorations') ||
46 messageOrDataObject.includes('m/decorationsRequest')
47 ) {
48 // Don't log publish decorations requests
49 } else {
50 // @ts-ignore This is just a utility function
51 Server.client.logTrace(messageOrDataObject, data);
52 }
53 } else {
54 // @ts-ignore
55 Server.client.logObjectTrace(messageOrDataObject);
56 }
57 }
58 };
35 Server.client.onReady().then(() => { 59 Server.client.onReady().then(() => {
36 for (const [type, handler] of notificationHandlers) { 60 for (const [type, handler] of notificationHandlers) {
37 Server.client.onNotification(type, handler); 61 Server.client.onNotification(type, handler);