aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Diebold <[email protected]>2018-12-23 11:05:54 +0000
committerFlorian Diebold <[email protected]>2018-12-23 12:48:04 +0000
commit7348f7883fa2bd571fff036c82e98c102d05c362 (patch)
treee7882097498b6d85e631d570dac0d8a89cd24875
parent3899898d75176ce3cd87f9e2acecd7e3a987dda5 (diff)
Add testing infrastructure for type inference
- move dir_tests to test_utils for that.
-rw-r--r--crates/ra_analysis/src/imp.rs14
-rw-r--r--crates/ra_analysis/src/lib.rs3
-rw-r--r--crates/ra_hir/src/mock.rs9
-rw-r--r--crates/ra_hir/src/ty.rs30
-rw-r--r--crates/ra_hir/src/ty/tests.rs62
-rw-r--r--crates/ra_hir/src/ty/tests/data/0001_basics.rs11
-rw-r--r--crates/ra_hir/src/ty/tests/data/0001_basics.txt13
-rw-r--r--crates/ra_syntax/tests/test.rs105
-rw-r--r--crates/test_utils/src/lib.rs99
9 files changed, 222 insertions, 124 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index b01382808..4e0631679 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}; 6use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit};
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,
@@ -510,6 +511,17 @@ impl AnalysisImpl {
510 Ok(None) 511 Ok(None)
511 } 512 }
512 513
514 pub fn type_of(&self, file_id: FileId, range: TextRange) -> Cancelable<Option<String>> {
515 let file = self.db.source_file(file_id);
516 let syntax = file.syntax();
517 let node = find_covering_node(syntax, range);
518 let parent_fn = node.ancestors().filter_map(FnDef::cast).next();
519 let parent_fn = if let Some(p) = parent_fn { p } else { return Ok(None) };
520 let function = ctry!(source_binder::function_from_source(&*self.db, file_id, parent_fn)?);
521 let infer = function.infer(&*self.db);
522 Ok(infer.type_of_node(node).map(|t| t.to_string()))
523 }
524
513 fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> { 525 fn index_resolve(&self, name_ref: ast::NameRef) -> Cancelable<Vec<(FileId, FileSymbol)>> {
514 let name = name_ref.text(); 526 let name = name_ref.text();
515 let mut query = Query::new(name.to_string()); 527 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 85df9c089..830898140 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_hir/src/mock.rs b/crates/ra_hir/src/mock.rs
index a9fa540d5..3020ee793 100644
--- a/crates/ra_hir/src/mock.rs
+++ b/crates/ra_hir/src/mock.rs
@@ -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 ( <|> )");
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index 087385b98..66b204dcd 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -6,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
6 6
7use std::sync::Arc; 7use std::sync::Arc;
8use std::collections::HashMap; 8use std::collections::HashMap;
9use std::fmt;
9 10
10use ra_db::LocalSyntaxPtr; 11use ra_db::LocalSyntaxPtr;
11use ra_syntax::{ 12use ra_syntax::{
@@ -184,11 +185,40 @@ impl Ty {
184 } 185 }
185} 186}
186 187
188impl fmt::Display for Ty {
189 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190 match self {
191 Ty::Bool => write!(f, "bool"),
192 Ty::Char => write!(f, "char"),
193 Ty::Int(t) => write!(f, "{}", t.ty_to_string()),
194 Ty::Uint(t) => write!(f, "{}", t.ty_to_string()),
195 Ty::Float(t) => write!(f, "{}", t.ty_to_string()),
196 Ty::Str => write!(f, "str"),
197 Ty::Slice(t) => write!(f, "[{}]", t),
198 Ty::Never => write!(f, "!"),
199 Ty::Tuple(ts) => {
200 write!(f, "(")?;
201 for t in ts {
202 write!(f, "{},", t)?;
203 }
204 write!(f, ")")
205 }
206 Ty::Unknown => write!(f, "[unknown]")
207 }
208 }
209}
210
187#[derive(Clone, PartialEq, Eq, Debug)] 211#[derive(Clone, PartialEq, Eq, Debug)]
188pub struct InferenceResult { 212pub struct InferenceResult {
189 type_for: FxHashMap<LocalSyntaxPtr, Ty>, 213 type_for: FxHashMap<LocalSyntaxPtr, Ty>,
190} 214}
191 215
216impl InferenceResult {
217 pub fn type_of_node(&self, node: SyntaxNodeRef) -> Option<Ty> {
218 self.type_for.get(&LocalSyntaxPtr::new(node)).cloned()
219 }
220}
221
192#[derive(Clone, PartialEq, Eq, Debug)] 222#[derive(Clone, PartialEq, Eq, Debug)]
193pub struct InferenceContext { 223pub struct InferenceContext {
194 scopes: Arc<FnScopes>, 224 scopes: Arc<FnScopes>,
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index f2466dd51..98eedaa3f 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -1,8 +1,11 @@
1use std::fmt::Write;
1use std::sync::Arc; 2use std::sync::Arc;
3use std::path::{Path, PathBuf};
2 4
3use salsa::Database; 5use salsa::Database;
4use ra_db::{FilesDatabase, CrateGraph, SyntaxDatabase}; 6use ra_db::{FilesDatabase, CrateGraph, SyntaxDatabase};
5use ra_syntax::{SmolStr, algo::visit::{visitor, Visitor}, ast::{self, AstNode}}; 7use ra_syntax::{SmolStr, algo::visit::{visitor, Visitor}, ast::{self, AstNode}};
8use test_utils::{project_dir, dir_tests};
6use relative_path::RelativePath; 9use relative_path::RelativePath;
7 10
8use crate::{source_binder, mock::WORKSPACE, module::ModuleSourceNode}; 11use crate::{source_binder, mock::WORKSPACE, module::ModuleSourceNode};
@@ -13,33 +16,46 @@ use crate::{
13 mock::MockDatabase, 16 mock::MockDatabase,
14}; 17};
15 18
16fn infer_all_fns(fixture: &str) -> () { 19fn infer_file(content: &str) -> String {
17 let (db, source_root) = MockDatabase::with_files(fixture); 20 let (db, source_root, file_id) = MockDatabase::with_single_file(content);
18 for &file_id in source_root.files.values() { 21 let source_file = db.source_file(file_id);
19 let source_file = db.source_file(file_id); 22 let mut acc = String::new();
20 for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) { 23 for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) {
21 let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap().unwrap(); 24 let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap().unwrap();
22 let inference_result = func.infer(&db); 25 let inference_result = func.infer(&db);
23 for (syntax_ptr, ty) in &inference_result.type_for { 26 for (syntax_ptr, ty) in &inference_result.type_for {
24 let node = syntax_ptr.resolve(&source_file); 27 let node = syntax_ptr.resolve(&source_file);
25 eprintln!("{} '{}': {:?}", syntax_ptr.range(), node.text(), ty); 28 write!(acc, "{} '{}': {}\n", syntax_ptr.range(), ellipsize(node.text().to_string().replace("\n", " "), 15), ty);
26 }
27 } 29 }
28 } 30 }
31 acc
32}
33
34fn ellipsize(mut text: String, max_len: usize) -> String {
35 if text.len() <= max_len {
36 return text;
37 }
38 let ellipsis = "...";
39 let e_len = ellipsis.len();
40 let mut prefix_len = (max_len - e_len) / 2;
41 while !text.is_char_boundary(prefix_len) {
42 prefix_len += 1;
43 }
44 let mut suffix_len = max_len - e_len - prefix_len;
45 while !text.is_char_boundary(text.len() - suffix_len) {
46 suffix_len += 1;
47 }
48 text.replace_range(prefix_len..text.len() - suffix_len, ellipsis);
49 text
29} 50}
30 51
31#[test] 52#[test]
32fn infer_smoke_test() { 53pub fn infer_tests() {
33 let text = " 54 dir_tests(&test_data_dir(), &["."], |text, _path| {
34 //- /lib.rs 55 infer_file(text)
35 fn foo(x: u32, y: !) -> i128 { 56 });
36 x; 57}
37 y;
38 return 1;
39 \"hello\";
40 0
41 }
42 ";
43 58
44 infer_all_fns(text); 59fn test_data_dir() -> PathBuf {
60 project_dir().join("crates/ra_hir/src/ty/tests/data")
45} 61}
diff --git a/crates/ra_hir/src/ty/tests/data/0001_basics.rs b/crates/ra_hir/src/ty/tests/data/0001_basics.rs
new file mode 100644
index 000000000..59a60d031
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/0001_basics.rs
@@ -0,0 +1,11 @@
1
2fn test(a: u32, b: isize, c: !, d: &str) {
3 a;
4 b;
5 c;
6 d;
7 1usize;
8 1isize;
9 "test";
10 1.0f32;
11}
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_syntax/tests/test.rs b/crates/ra_syntax/tests/test.rs
index 4266864bd..9d94a1a23 100644
--- a/crates/ra_syntax/tests/test.rs
+++ b/crates/ra_syntax/tests/test.rs
@@ -9,6 +9,7 @@ use std::{
9 path::{Path, PathBuf, Component}, 9 path::{Path, PathBuf, Component},
10}; 10};
11 11
12use test_utils::{project_dir, dir_tests, read_text, collect_tests};
12use ra_syntax::{ 13use ra_syntax::{
13 utils::{check_fuzz_invariants, dump_tree}, 14 utils::{check_fuzz_invariants, dump_tree},
14 SourceFileNode, 15 SourceFileNode,
@@ -16,7 +17,7 @@ use ra_syntax::{
16 17
17#[test] 18#[test]
18fn lexer_tests() { 19fn lexer_tests() {
19 dir_tests(&["lexer"], |text, _| { 20 dir_tests(&test_data_dir(), &["lexer"], |text, _| {
20 let tokens = ra_syntax::tokenize(text); 21 let tokens = ra_syntax::tokenize(text);
21 dump_tokens(&tokens, text) 22 dump_tokens(&tokens, text)
22 }) 23 })
@@ -24,7 +25,7 @@ fn lexer_tests() {
24 25
25#[test] 26#[test]
26fn parser_tests() { 27fn parser_tests() {
27 dir_tests(&["parser/inline/ok", "parser/ok"], |text, path| { 28 dir_tests(&test_data_dir(), &["parser/inline/ok", "parser/ok"], |text, path| {
28 let file = SourceFileNode::parse(text); 29 let file = SourceFileNode::parse(text);
29 let errors = file.errors(); 30 let errors = file.errors();
30 assert_eq!( 31 assert_eq!(
@@ -35,7 +36,7 @@ fn parser_tests() {
35 ); 36 );
36 dump_tree(file.syntax()) 37 dump_tree(file.syntax())
37 }); 38 });
38 dir_tests(&["parser/err", "parser/inline/err"], |text, path| { 39 dir_tests(&test_data_dir(), &["parser/err", "parser/inline/err"], |text, path| {
39 let file = SourceFileNode::parse(text); 40 let file = SourceFileNode::parse(text);
40 let errors = file.errors(); 41 let errors = file.errors();
41 assert_ne!( 42 assert_ne!(
@@ -50,7 +51,7 @@ fn parser_tests() {
50 51
51#[test] 52#[test]
52fn parser_fuzz_tests() { 53fn parser_fuzz_tests() {
53 for (_, text) in collect_tests(&["parser/fuzz-failures"]) { 54 for (_, text) in collect_tests(&test_data_dir(), &["parser/fuzz-failures"]) {
54 check_fuzz_invariants(&text) 55 check_fuzz_invariants(&text)
55 } 56 }
56} 57}
@@ -92,102 +93,6 @@ fn self_hosting_parsing() {
92 "self_hosting_parsing found too few files - is it running in the right directory?" 93 "self_hosting_parsing found too few files - is it running in the right directory?"
93 ) 94 )
94} 95}
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 96
192fn test_data_dir() -> PathBuf { 97fn test_data_dir() -> PathBuf {
193 project_dir().join("crates/ra_syntax/tests/data") 98 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}