diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-12 21:18:14 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-12 21:18:14 +0000 |
commit | eb931c0d9e0877e573622253ae5b05563841037b (patch) | |
tree | 653ef81450a4d39c5b46f98c97c23fa8586dd7f8 /crates/ra_hir/src/ty/method_resolution.rs | |
parent | e56072bfa3e5af69a4c293a38de6e1350ada3573 (diff) | |
parent | 1ed7fbfc1badd2c2a42b4dc2feb1b4bf7835d3ef (diff) |
Merge #505
505: Inherent methods r=matklad a=flodiebold
This adds resolution, type checking and completion for inherent methods.
The main open question here is the caching, I think. I'm not sure whether we should be caching method resolutions in a more fine grained way (currently we just build a hash map of types -> impl blocks, and iterate through all potential impl blocks when looking for a method).
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/ty/method_resolution.rs')
-rw-r--r-- | crates/ra_hir/src/ty/method_resolution.rs | 164 |
1 files changed, 164 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..7c3839388 --- /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 | pub(crate) fn impls_in_crate_query( | ||
86 | db: &impl HirDatabase, | ||
87 | krate: Crate, | ||
88 | ) -> Cancelable<Arc<CrateImplBlocks>> { | ||
89 | let crate_graph = db.crate_graph(); | ||
90 | let file_id = crate_graph.crate_root(krate.crate_id); | ||
91 | let source_root_id = db.file_source_root(file_id); | ||
92 | let mut crate_impl_blocks = CrateImplBlocks { | ||
93 | source_root_id, | ||
94 | impls: FxHashMap::default(), | ||
95 | }; | ||
96 | if let Some(module) = krate.root_module(db)? { | ||
97 | crate_impl_blocks.collect_recursive(db, module)?; | ||
98 | } | ||
99 | Ok(Arc::new(crate_impl_blocks)) | ||
100 | } | ||
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_param() { | ||
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 | } | ||