aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2018-12-24 14:40:11 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2018-12-24 14:40:11 +0000
commit67e768466ff2e2611eead0f30b2e9c4083c80c20 (patch)
tree8984028019837c91131fc30f60eecf8c2a457368 /crates/ra_hir
parentabe09eb5edfe8f4c58baa16140acbd414635836f (diff)
parent4befde1eee5b1e2b7ddc9bf764b77f82b792c318 (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')
-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
14 files changed, 969 insertions, 7 deletions
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