diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-24 14:40:11 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2018-12-24 14:40:11 +0000 |
commit | 67e768466ff2e2611eead0f30b2e9c4083c80c20 (patch) | |
tree | 8984028019837c91131fc30f60eecf8c2a457368 /crates/ra_hir/src/ty | |
parent | abe09eb5edfe8f4c58baa16140acbd414635836f (diff) | |
parent | 4befde1eee5b1e2b7ddc9bf764b77f82b792c318 (diff) |
Merge #327
327: Beginnings of type inference r=flodiebold a=flodiebold
I was a bit bored, so I thought I'd try to start implementing the type system and see how far I come :wink: This is obviously still extremely WIP, only very basic stuff working, but I thought I'd post this now to get some feedback as to whether this approach makes sense at all.
There's no user-visible effect yet, but the type inference has tests similar to the ones for the parser. My next step will probably be to implement struct types, after which this could probably be used to complete fields.
I realize this may all get thrown away when/if the compiler query system gets usable, but I feel like there are lots of IDE features that could be implemented with somewhat working type inference in the meantime :smile:
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/ty')
-rw-r--r-- | crates/ra_hir/src/ty/primitive.rs | 130 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests.rs | 134 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests/data/0001_basics.txt | 13 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests/data/0002_let.txt | 7 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests/data/0003_paths.txt | 9 |
5 files changed, 293 insertions, 0 deletions
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 @@ | |||
1 | use std::fmt; | ||
2 | |||
3 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)] | ||
4 | pub enum IntTy { | ||
5 | Isize, | ||
6 | I8, | ||
7 | I16, | ||
8 | I32, | ||
9 | I64, | ||
10 | I128, | ||
11 | } | ||
12 | |||
13 | impl fmt::Debug for IntTy { | ||
14 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
15 | fmt::Display::fmt(self, f) | ||
16 | } | ||
17 | } | ||
18 | |||
19 | impl fmt::Display for IntTy { | ||
20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
21 | write!(f, "{}", self.ty_to_string()) | ||
22 | } | ||
23 | } | ||
24 | |||
25 | impl 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)] | ||
51 | pub enum UintTy { | ||
52 | Usize, | ||
53 | U8, | ||
54 | U16, | ||
55 | U32, | ||
56 | U64, | ||
57 | U128, | ||
58 | } | ||
59 | |||
60 | impl 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 | |||
85 | impl fmt::Debug for UintTy { | ||
86 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
87 | fmt::Display::fmt(self, f) | ||
88 | } | ||
89 | } | ||
90 | |||
91 | impl 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)] | ||
98 | pub enum FloatTy { | ||
99 | F32, | ||
100 | F64, | ||
101 | } | ||
102 | |||
103 | impl fmt::Debug for FloatTy { | ||
104 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
105 | fmt::Display::fmt(self, f) | ||
106 | } | ||
107 | } | ||
108 | |||
109 | impl fmt::Display for FloatTy { | ||
110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
111 | write!(f, "{}", self.ty_to_string()) | ||
112 | } | ||
113 | } | ||
114 | |||
115 | impl 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 @@ | |||
1 | use std::fmt::Write; | ||
2 | use std::path::{PathBuf, Path}; | ||
3 | use std::fs; | ||
4 | |||
5 | use ra_db::{SyntaxDatabase}; | ||
6 | use ra_syntax::ast::{self, AstNode}; | ||
7 | use test_utils::{project_dir, assert_eq_text, read_text}; | ||
8 | |||
9 | use 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] | ||
21 | fn infer_basics() { | ||
22 | check_inference( | ||
23 | r#" | ||
24 | fn 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] | ||
39 | fn infer_let() { | ||
40 | check_inference( | ||
41 | r#" | ||
42 | fn 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] | ||
53 | fn infer_paths() { | ||
54 | check_inference( | ||
55 | r#" | ||
56 | fn a() -> u32 { 1 } | ||
57 | |||
58 | mod b { | ||
59 | fn c() -> u32 { 1 } | ||
60 | } | ||
61 | |||
62 | fn test() { | ||
63 | a(); | ||
64 | b::c(); | ||
65 | } | ||
66 | }"#, | ||
67 | "0003_paths.txt", | ||
68 | ); | ||
69 | } | ||
70 | |||
71 | fn 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 | |||
99 | fn 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 | |||
114 | fn 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 | |||
132 | fn 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 | ||