diff options
author | Florian Diebold <[email protected]> | 2019-01-07 12:44:54 +0000 |
---|---|---|
committer | Florian Diebold <[email protected]> | 2019-01-12 14:01:19 +0000 |
commit | 082ef52bcb15d779c6aff78d9860d328bf7df9b2 (patch) | |
tree | a4a123ad491e15c8d17bdc815675d43bb33811b5 /crates/ra_hir/src/ty | |
parent | e9e397e705ad0bec9775067b10109e35ebefc493 (diff) |
Implement basic inherent method resolution
Diffstat (limited to 'crates/ra_hir/src/ty')
-rw-r--r-- | crates/ra_hir/src/ty/method_resolution.rs | 164 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests.rs | 26 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests/data/inherent_method.txt | 18 |
3 files changed, 208 insertions, 0 deletions
diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs new file mode 100644 index 000000000..ad80aa8b6 --- /dev/null +++ b/crates/ra_hir/src/ty/method_resolution.rs | |||
@@ -0,0 +1,164 @@ | |||
1 | //! This module is concerned with finding methods that a given type provides. | ||
2 | //! For details about how this works in rustc, see the method lookup page in the | ||
3 | //! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html) | ||
4 | //! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. | ||
5 | use std::sync::Arc; | ||
6 | |||
7 | use rustc_hash::FxHashMap; | ||
8 | |||
9 | use ra_db::{Cancelable, SourceRootId}; | ||
10 | |||
11 | use crate::{HirDatabase, DefId, module_tree::ModuleId, Module, Crate, Name, Function, impl_block::{ImplId, ImplBlock, ImplItem}}; | ||
12 | use super::Ty; | ||
13 | |||
14 | /// This is used as a key for indexing impls. | ||
15 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] | ||
16 | pub enum TyFingerprint { | ||
17 | Adt(DefId), | ||
18 | // we'll also want to index impls for primitive types etc. | ||
19 | } | ||
20 | |||
21 | impl TyFingerprint { | ||
22 | /// Creates a TyFingerprint for looking up an impl. Only certain types can | ||
23 | /// have impls: if we have some `struct S`, we can have an `impl S`, but not | ||
24 | /// `impl &S`. Hence, this will return `None` for reference types and such. | ||
25 | fn for_impl(ty: &Ty) -> Option<TyFingerprint> { | ||
26 | match ty { | ||
27 | Ty::Adt { def_id, .. } => Some(TyFingerprint::Adt(*def_id)), | ||
28 | _ => None, | ||
29 | } | ||
30 | } | ||
31 | } | ||
32 | |||
33 | #[derive(Debug, PartialEq, Eq)] | ||
34 | pub struct CrateImplBlocks { | ||
35 | /// To make sense of the ModuleIds, we need the source root. | ||
36 | source_root_id: SourceRootId, | ||
37 | impls: FxHashMap<TyFingerprint, Vec<(ModuleId, ImplId)>>, | ||
38 | } | ||
39 | |||
40 | impl CrateImplBlocks { | ||
41 | pub fn lookup_impl_blocks<'a>( | ||
42 | &'a self, | ||
43 | db: &'a impl HirDatabase, | ||
44 | ty: &Ty, | ||
45 | ) -> impl Iterator<Item = Cancelable<ImplBlock>> + 'a { | ||
46 | let fingerprint = TyFingerprint::for_impl(ty); | ||
47 | fingerprint | ||
48 | .and_then(|f| self.impls.get(&f)) | ||
49 | .into_iter() | ||
50 | .flat_map(|i| i.iter()) | ||
51 | .map(move |(module_id, impl_id)| { | ||
52 | let module_impl_blocks = db.impls_in_module(self.source_root_id, *module_id)?; | ||
53 | Ok(ImplBlock::from_id(module_impl_blocks, *impl_id)) | ||
54 | }) | ||
55 | } | ||
56 | |||
57 | fn collect_recursive(&mut self, db: &impl HirDatabase, module: Module) -> Cancelable<()> { | ||
58 | let module_id = module.def_id.loc(db).module_id; | ||
59 | let module_impl_blocks = db.impls_in_module(self.source_root_id, module_id)?; | ||
60 | |||
61 | for (impl_id, impl_data) in module_impl_blocks.impls.iter() { | ||
62 | let impl_block = ImplBlock::from_id(Arc::clone(&module_impl_blocks), impl_id); | ||
63 | |||
64 | if let Some(_target_trait) = impl_data.target_trait() { | ||
65 | // ignore for now | ||
66 | } else { | ||
67 | let target_ty = | ||
68 | Ty::from_hir(db, &module, Some(&impl_block), impl_data.target_type())?; | ||
69 | if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { | ||
70 | self.impls | ||
71 | .entry(target_ty_fp) | ||
72 | .or_insert_with(Vec::new) | ||
73 | .push((module_id, impl_id)); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | for child in module.children(db)? { | ||
79 | self.collect_recursive(db, child)?; | ||
80 | } | ||
81 | |||
82 | Ok(()) | ||
83 | } | ||
84 | } | ||
85 | |||
86 | pub(crate) fn impls_in_crate( | ||
87 | db: &impl HirDatabase, | ||
88 | krate: Crate, | ||
89 | ) -> Cancelable<Arc<CrateImplBlocks>> { | ||
90 | let crate_graph = db.crate_graph(); | ||
91 | let file_id = crate_graph.crate_root(krate.crate_id); | ||
92 | let source_root_id = db.file_source_root(file_id); | ||
93 | let mut crate_impl_blocks = CrateImplBlocks { | ||
94 | source_root_id, | ||
95 | impls: FxHashMap::default(), | ||
96 | }; | ||
97 | if let Some(module) = krate.root_module(db)? { | ||
98 | crate_impl_blocks.collect_recursive(db, module)?; | ||
99 | } | ||
100 | Ok(Arc::new(crate_impl_blocks)) | ||
101 | } | ||
102 | |||
103 | fn def_crate(db: &impl HirDatabase, ty: &Ty) -> Cancelable<Option<Crate>> { | ||
104 | match ty { | ||
105 | Ty::Adt { def_id, .. } => def_id.krate(db), | ||
106 | _ => Ok(None), | ||
107 | } | ||
108 | } | ||
109 | |||
110 | impl Ty { | ||
111 | // TODO: cache this as a query? | ||
112 | // - if so, what signature? (TyFingerprint, Name)? | ||
113 | // - or maybe cache all names and def_ids of methods per fingerprint? | ||
114 | pub fn lookup_method(self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<DefId>> { | ||
115 | self.iterate_methods(db, |f| { | ||
116 | let sig = f.signature(db); | ||
117 | if sig.name() == name && sig.has_self_arg() { | ||
118 | Ok(Some(f.def_id())) | ||
119 | } else { | ||
120 | Ok(None) | ||
121 | } | ||
122 | }) | ||
123 | } | ||
124 | |||
125 | // This would be nicer if it just returned an iterator, but that's really | ||
126 | // complicated with all the cancelable operations | ||
127 | pub fn iterate_methods<T>( | ||
128 | self, | ||
129 | db: &impl HirDatabase, | ||
130 | mut callback: impl FnMut(Function) -> Cancelable<Option<T>>, | ||
131 | ) -> Cancelable<Option<T>> { | ||
132 | // For method calls, rust first does any number of autoderef, and then one | ||
133 | // autoref (i.e. when the method takes &self or &mut self). We just ignore | ||
134 | // the autoref currently -- when we find a method matching the given name, | ||
135 | // we assume it fits. | ||
136 | |||
137 | // Also note that when we've got a receiver like &S, even if the method we | ||
138 | // find in the end takes &self, we still do the autoderef step (just as | ||
139 | // rustc does an autoderef and then autoref again). | ||
140 | |||
141 | for derefed_ty in self.autoderef(db) { | ||
142 | let krate = match def_crate(db, &derefed_ty)? { | ||
143 | Some(krate) => krate, | ||
144 | None => continue, | ||
145 | }; | ||
146 | let impls = db.impls_in_crate(krate)?; | ||
147 | |||
148 | for impl_block in impls.lookup_impl_blocks(db, &derefed_ty) { | ||
149 | let impl_block = impl_block?; | ||
150 | for item in impl_block.items() { | ||
151 | match item { | ||
152 | ImplItem::Method(f) => { | ||
153 | if let Some(result) = callback(f.clone())? { | ||
154 | return Ok(Some(result)); | ||
155 | } | ||
156 | } | ||
157 | _ => {} | ||
158 | } | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | Ok(None) | ||
163 | } | ||
164 | } | ||
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 815aecda7..1c3129441 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs | |||
@@ -242,6 +242,32 @@ fn test() { | |||
242 | ); | 242 | ); |
243 | } | 243 | } |
244 | 244 | ||
245 | #[test] | ||
246 | fn infer_inherent_method() { | ||
247 | check_inference( | ||
248 | r#" | ||
249 | struct A; | ||
250 | |||
251 | impl A { | ||
252 | fn foo(self, x: u32) -> i32 {} | ||
253 | } | ||
254 | |||
255 | mod b { | ||
256 | impl super::A { | ||
257 | fn bar(&self, x: u64) -> i64 {} | ||
258 | } | ||
259 | } | ||
260 | |||
261 | fn test(a: A) { | ||
262 | a.foo(1); | ||
263 | (&a).bar(1); | ||
264 | a.bar(1); | ||
265 | } | ||
266 | "#, | ||
267 | "inherent_method.txt", | ||
268 | ); | ||
269 | } | ||
270 | |||
245 | fn infer(content: &str) -> String { | 271 | fn infer(content: &str) -> String { |
246 | let (db, _, file_id) = MockDatabase::with_single_file(content); | 272 | let (db, _, file_id) = MockDatabase::with_single_file(content); |
247 | let source_file = db.source_file(file_id); | 273 | let source_file = db.source_file(file_id); |
diff --git a/crates/ra_hir/src/ty/tests/data/inherent_method.txt b/crates/ra_hir/src/ty/tests/data/inherent_method.txt new file mode 100644 index 000000000..6e6f70357 --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/inherent_method.txt | |||
@@ -0,0 +1,18 @@ | |||
1 | [32; 36) 'self': A | ||
2 | [38; 39) 'x': u32 | ||
3 | [53; 55) '{}': () | ||
4 | [103; 107) 'self': &A | ||
5 | [109; 110) 'x': u64 | ||
6 | [124; 126) '{}': () | ||
7 | [144; 145) 'a': A | ||
8 | [150; 198) '{ ...(1); }': () | ||
9 | [156; 157) 'a': A | ||
10 | [156; 164) 'a.foo(1)': i32 | ||
11 | [162; 163) '1': u32 | ||
12 | [170; 181) '(&a).bar(1)': i64 | ||
13 | [171; 173) '&a': &A | ||
14 | [172; 173) 'a': A | ||
15 | [179; 180) '1': u64 | ||
16 | [187; 188) 'a': A | ||
17 | [187; 195) 'a.bar(1)': i64 | ||
18 | [193; 194) '1': u64 | ||