diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-14 20:55:18 +0100 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-04-14 20:55:18 +0100 |
commit | e1a2649aff0a9387fb14646a56cb652061bc42ec (patch) | |
tree | b8275843aa56b922b6325b50be2aae063234cc2a /crates/ra_hir | |
parent | 88be6f32172813f53dae60d73c9f5deb0c3fb29f (diff) | |
parent | 4f8a49f43cad086a656626c43062ff89b46f505a (diff) |
Merge #1144
1144: Refactor method candidate generation a bit r=flodiebold a=flodiebold
This fixes the order in which candidates are chosen a bit (not completely though, as the ignored test demonstrates), and makes autoderef work with trait methods.
As a side effect, this also makes completion of trait methods work :)
Co-authored-by: Florian Diebold <[email protected]>
Diffstat (limited to 'crates/ra_hir')
-rw-r--r-- | crates/ra_hir/src/source_binder.rs | 12 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/method_resolution.rs | 126 | ||||
-rw-r--r-- | crates/ra_hir/src/ty/tests.rs | 65 |
3 files changed, 149 insertions, 54 deletions
diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index bd035ced9..f1bb13bc6 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs | |||
@@ -18,7 +18,7 @@ use ra_syntax::{ | |||
18 | 18 | ||
19 | use crate::{ | 19 | use crate::{ |
20 | HirDatabase, Function, Struct, Enum, Const, Static, Either, DefWithBody, PerNs, Name, | 20 | HirDatabase, Function, Struct, Enum, Const, Static, Either, DefWithBody, PerNs, Name, |
21 | AsName, Module, HirFileId, Crate, Trait, Resolver, | 21 | AsName, Module, HirFileId, Crate, Trait, Resolver, Ty, |
22 | expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, | 22 | expr::{BodySourceMap, scope::{ScopeId, ExprScopes}}, |
23 | ids::LocationCtx, | 23 | ids::LocationCtx, |
24 | expr, AstId | 24 | expr, AstId |
@@ -343,6 +343,16 @@ impl SourceAnalyzer { | |||
343 | .collect() | 343 | .collect() |
344 | } | 344 | } |
345 | 345 | ||
346 | pub fn iterate_method_candidates<T>( | ||
347 | &self, | ||
348 | db: &impl HirDatabase, | ||
349 | ty: Ty, | ||
350 | name: Option<&Name>, | ||
351 | callback: impl FnMut(&Ty, Function) -> Option<T>, | ||
352 | ) -> Option<T> { | ||
353 | ty.iterate_method_candidates(db, &self.resolver, name, callback) | ||
354 | } | ||
355 | |||
346 | #[cfg(test)] | 356 | #[cfg(test)] |
347 | pub(crate) fn body_source_map(&self) -> Arc<BodySourceMap> { | 357 | pub(crate) fn body_source_map(&self) -> Arc<BodySourceMap> { |
348 | self.body_source_map.clone().unwrap() | 358 | self.body_source_map.clone().unwrap() |
diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 6b7918187..667b66095 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs | |||
@@ -129,85 +129,105 @@ impl Ty { | |||
129 | name: &Name, | 129 | name: &Name, |
130 | resolver: &Resolver, | 130 | resolver: &Resolver, |
131 | ) -> Option<(Ty, Function)> { | 131 | ) -> Option<(Ty, Function)> { |
132 | // FIXME: trait methods should be used before autoderefs | 132 | self.iterate_method_candidates(db, resolver, Some(name), |ty, f| Some((ty.clone(), f))) |
133 | // (and we need to do autoderefs for trait method calls as well) | ||
134 | let inherent_method = self.clone().iterate_methods(db, |ty, f| { | ||
135 | let sig = f.signature(db); | ||
136 | if sig.name() == name && sig.has_self_param() { | ||
137 | Some((ty.clone(), f)) | ||
138 | } else { | ||
139 | None | ||
140 | } | ||
141 | }); | ||
142 | inherent_method.or_else(|| self.lookup_trait_method(db, name, resolver)) | ||
143 | } | 133 | } |
144 | 134 | ||
145 | fn lookup_trait_method( | 135 | // This would be nicer if it just returned an iterator, but that runs into |
136 | // lifetime problems, because we need to borrow temp `CrateImplBlocks`. | ||
137 | pub(crate) fn iterate_method_candidates<T>( | ||
146 | self, | 138 | self, |
147 | db: &impl HirDatabase, | 139 | db: &impl HirDatabase, |
148 | name: &Name, | ||
149 | resolver: &Resolver, | 140 | resolver: &Resolver, |
150 | ) -> Option<(Ty, Function)> { | 141 | name: Option<&Name>, |
151 | let mut candidates = Vec::new(); | 142 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, |
152 | for t in resolver.traits_in_scope() { | 143 | ) -> Option<T> { |
144 | // For method calls, rust first does any number of autoderef, and then one | ||
145 | // autoref (i.e. when the method takes &self or &mut self). We just ignore | ||
146 | // the autoref currently -- when we find a method matching the given name, | ||
147 | // we assume it fits. | ||
148 | |||
149 | // Also note that when we've got a receiver like &S, even if the method we | ||
150 | // find in the end takes &self, we still do the autoderef step (just as | ||
151 | // rustc does an autoderef and then autoref again). | ||
152 | |||
153 | for derefed_ty in self.autoderef(db) { | ||
154 | if let Some(result) = derefed_ty.iterate_inherent_methods(db, name, &mut callback) { | ||
155 | return Some(result); | ||
156 | } | ||
157 | if let Some(result) = | ||
158 | derefed_ty.iterate_trait_method_candidates(db, resolver, name, &mut callback) | ||
159 | { | ||
160 | return Some(result); | ||
161 | } | ||
162 | } | ||
163 | None | ||
164 | } | ||
165 | |||
166 | fn iterate_trait_method_candidates<T>( | ||
167 | &self, | ||
168 | db: &impl HirDatabase, | ||
169 | resolver: &Resolver, | ||
170 | name: Option<&Name>, | ||
171 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, | ||
172 | ) -> Option<T> { | ||
173 | 'traits: for t in resolver.traits_in_scope() { | ||
153 | let data = t.trait_data(db); | 174 | let data = t.trait_data(db); |
175 | // we'll be lazy about checking whether the type implements the | ||
176 | // trait, but if we find out it doesn't, we'll skip the rest of the | ||
177 | // iteration | ||
178 | let mut known_implemented = false; | ||
154 | for item in data.items() { | 179 | for item in data.items() { |
155 | match item { | 180 | match item { |
156 | &TraitItem::Function(m) => { | 181 | &TraitItem::Function(m) => { |
157 | let sig = m.signature(db); | 182 | let sig = m.signature(db); |
158 | if sig.name() == name && sig.has_self_param() { | 183 | if name.map_or(true, |name| sig.name() == name) && sig.has_self_param() { |
159 | candidates.push((t, m)); | 184 | if !known_implemented { |
185 | let trait_ref = TraitRef { | ||
186 | trait_: t, | ||
187 | substs: fresh_substs_for_trait(db, t, self.clone()), | ||
188 | }; | ||
189 | let (trait_ref, _) = super::traits::canonicalize(trait_ref); | ||
190 | if db.implements(trait_ref).is_none() { | ||
191 | continue 'traits; | ||
192 | } | ||
193 | } | ||
194 | known_implemented = true; | ||
195 | if let Some(result) = callback(self, m) { | ||
196 | return Some(result); | ||
197 | } | ||
160 | } | 198 | } |
161 | } | 199 | } |
162 | _ => {} | 200 | _ => {} |
163 | } | 201 | } |
164 | } | 202 | } |
165 | } | 203 | } |
166 | candidates.retain(|(t, _m)| { | 204 | None |
167 | let trait_ref = | ||
168 | TraitRef { trait_: *t, substs: fresh_substs_for_trait(db, *t, self.clone()) }; | ||
169 | let (trait_ref, _) = super::traits::canonicalize(trait_ref); | ||
170 | db.implements(trait_ref).is_some() | ||
171 | }); | ||
172 | // FIXME if there's multiple candidates here, that's an ambiguity error | ||
173 | let (_chosen_trait, chosen_method) = candidates.first()?; | ||
174 | // FIXME return correct receiver type | ||
175 | Some((self.clone(), *chosen_method)) | ||
176 | } | 205 | } |
177 | 206 | ||
178 | // This would be nicer if it just returned an iterator, but that runs into | 207 | fn iterate_inherent_methods<T>( |
179 | // lifetime problems, because we need to borrow temp `CrateImplBlocks`. | 208 | &self, |
180 | pub fn iterate_methods<T>( | ||
181 | self, | ||
182 | db: &impl HirDatabase, | 209 | db: &impl HirDatabase, |
210 | name: Option<&Name>, | ||
183 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, | 211 | mut callback: impl FnMut(&Ty, Function) -> Option<T>, |
184 | ) -> Option<T> { | 212 | ) -> Option<T> { |
185 | // For method calls, rust first does any number of autoderef, and then one | 213 | let krate = match def_crate(db, self) { |
186 | // autoref (i.e. when the method takes &self or &mut self). We just ignore | 214 | Some(krate) => krate, |
187 | // the autoref currently -- when we find a method matching the given name, | 215 | None => return None, |
188 | // we assume it fits. | 216 | }; |
189 | 217 | let impls = db.impls_in_crate(krate); | |
190 | // Also note that when we've got a receiver like &S, even if the method we | ||
191 | // find in the end takes &self, we still do the autoderef step (just as | ||
192 | // rustc does an autoderef and then autoref again). | ||
193 | 218 | ||
194 | for derefed_ty in self.autoderef(db) { | 219 | for impl_block in impls.lookup_impl_blocks(self) { |
195 | let krate = match def_crate(db, &derefed_ty) { | 220 | for item in impl_block.items(db) { |
196 | Some(krate) => krate, | 221 | match item { |
197 | None => continue, | 222 | ImplItem::Method(f) => { |
198 | }; | 223 | let sig = f.signature(db); |
199 | let impls = db.impls_in_crate(krate); | 224 | if name.map_or(true, |name| sig.name() == name) && sig.has_self_param() { |
200 | 225 | if let Some(result) = callback(self, f) { | |
201 | for impl_block in impls.lookup_impl_blocks(&derefed_ty) { | ||
202 | for item in impl_block.items(db) { | ||
203 | match item { | ||
204 | ImplItem::Method(f) => { | ||
205 | if let Some(result) = callback(&derefed_ty, f) { | ||
206 | return Some(result); | 226 | return Some(result); |
207 | } | 227 | } |
208 | } | 228 | } |
209 | _ => {} | ||
210 | } | 229 | } |
230 | _ => {} | ||
211 | } | 231 | } |
212 | } | 232 | } |
213 | } | 233 | } |
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 291bc9ae5..82c4aeddb 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs | |||
@@ -2336,6 +2336,66 @@ fn test() -> u64 { | |||
2336 | ); | 2336 | ); |
2337 | } | 2337 | } |
2338 | 2338 | ||
2339 | #[ignore] | ||
2340 | #[test] | ||
2341 | fn method_resolution_trait_before_autoref() { | ||
2342 | let t = type_at( | ||
2343 | r#" | ||
2344 | //- /main.rs | ||
2345 | trait Trait { fn foo(self) -> u128; } | ||
2346 | struct S; | ||
2347 | impl S { fn foo(&self) -> i8 { 0 } } | ||
2348 | impl Trait for S { fn foo(self) -> u128 { 0 } } | ||
2349 | fn test() { S.foo()<|>; } | ||
2350 | "#, | ||
2351 | ); | ||
2352 | assert_eq!(t, "u128"); | ||
2353 | } | ||
2354 | |||
2355 | #[test] | ||
2356 | fn method_resolution_trait_before_autoderef() { | ||
2357 | let t = type_at( | ||
2358 | r#" | ||
2359 | //- /main.rs | ||
2360 | trait Trait { fn foo(self) -> u128; } | ||
2361 | struct S; | ||
2362 | impl S { fn foo(self) -> i8 { 0 } } | ||
2363 | impl Trait for &S { fn foo(self) -> u128 { 0 } } | ||
2364 | fn test() { (&S).foo()<|>; } | ||
2365 | "#, | ||
2366 | ); | ||
2367 | assert_eq!(t, "u128"); | ||
2368 | } | ||
2369 | |||
2370 | #[test] | ||
2371 | fn method_resolution_impl_before_trait() { | ||
2372 | let t = type_at( | ||
2373 | r#" | ||
2374 | //- /main.rs | ||
2375 | trait Trait { fn foo(self) -> u128; } | ||
2376 | struct S; | ||
2377 | impl S { fn foo(self) -> i8 { 0 } } | ||
2378 | impl Trait for S { fn foo(self) -> u128 { 0 } } | ||
2379 | fn test() { S.foo()<|>; } | ||
2380 | "#, | ||
2381 | ); | ||
2382 | assert_eq!(t, "i8"); | ||
2383 | } | ||
2384 | |||
2385 | #[test] | ||
2386 | fn method_resolution_trait_autoderef() { | ||
2387 | let t = type_at( | ||
2388 | r#" | ||
2389 | //- /main.rs | ||
2390 | trait Trait { fn foo(self) -> u128; } | ||
2391 | struct S; | ||
2392 | impl Trait for S { fn foo(self) -> u128 { 0 } } | ||
2393 | fn test() { (&S).foo()<|>; } | ||
2394 | "#, | ||
2395 | ); | ||
2396 | assert_eq!(t, "u128"); | ||
2397 | } | ||
2398 | |||
2339 | fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { | 2399 | fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { |
2340 | let file = db.parse(pos.file_id); | 2400 | let file = db.parse(pos.file_id); |
2341 | let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap(); | 2401 | let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap(); |
@@ -2344,6 +2404,11 @@ fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { | |||
2344 | ty.display(db).to_string() | 2404 | ty.display(db).to_string() |
2345 | } | 2405 | } |
2346 | 2406 | ||
2407 | fn type_at(content: &str) -> String { | ||
2408 | let (db, file_pos) = MockDatabase::with_position(content); | ||
2409 | type_at_pos(&db, file_pos) | ||
2410 | } | ||
2411 | |||
2347 | fn infer(content: &str) -> String { | 2412 | fn infer(content: &str) -> String { |
2348 | let (db, _, file_id) = MockDatabase::with_single_file(content); | 2413 | let (db, _, file_id) = MockDatabase::with_single_file(content); |
2349 | let source_file = db.parse(file_id); | 2414 | let source_file = db.parse(file_id); |