aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorFlorian Diebold <[email protected]>2019-04-14 15:08:10 +0100
committerFlorian Diebold <[email protected]>2019-04-14 20:53:35 +0100
commit4f8a49f43cad086a656626c43062ff89b46f505a (patch)
treeb8275843aa56b922b6325b50be2aae063234cc2a /crates
parent88be6f32172813f53dae60d73c9f5deb0c3fb29f (diff)
Refactor method candidate generation a bit
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 :)
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/source_binder.rs12
-rw-r--r--crates/ra_hir/src/ty/method_resolution.rs126
-rw-r--r--crates/ra_hir/src/ty/tests.rs65
-rw-r--r--crates/ra_ide_api/src/completion/complete_dot.rs28
4 files changed, 176 insertions, 55 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
19use crate::{ 19use 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]
2341fn method_resolution_trait_before_autoref() {
2342 let t = type_at(
2343 r#"
2344//- /main.rs
2345trait Trait { fn foo(self) -> u128; }
2346struct S;
2347impl S { fn foo(&self) -> i8 { 0 } }
2348impl Trait for S { fn foo(self) -> u128 { 0 } }
2349fn test() { S.foo()<|>; }
2350"#,
2351 );
2352 assert_eq!(t, "u128");
2353}
2354
2355#[test]
2356fn method_resolution_trait_before_autoderef() {
2357 let t = type_at(
2358 r#"
2359//- /main.rs
2360trait Trait { fn foo(self) -> u128; }
2361struct S;
2362impl S { fn foo(self) -> i8 { 0 } }
2363impl Trait for &S { fn foo(self) -> u128 { 0 } }
2364fn test() { (&S).foo()<|>; }
2365"#,
2366 );
2367 assert_eq!(t, "u128");
2368}
2369
2370#[test]
2371fn method_resolution_impl_before_trait() {
2372 let t = type_at(
2373 r#"
2374//- /main.rs
2375trait Trait { fn foo(self) -> u128; }
2376struct S;
2377impl S { fn foo(self) -> i8 { 0 } }
2378impl Trait for S { fn foo(self) -> u128 { 0 } }
2379fn test() { S.foo()<|>; }
2380"#,
2381 );
2382 assert_eq!(t, "i8");
2383}
2384
2385#[test]
2386fn method_resolution_trait_autoderef() {
2387 let t = type_at(
2388 r#"
2389//- /main.rs
2390trait Trait { fn foo(self) -> u128; }
2391struct S;
2392impl Trait for S { fn foo(self) -> u128 { 0 } }
2393fn test() { (&S).foo()<|>; }
2394"#,
2395 );
2396 assert_eq!(t, "u128");
2397}
2398
2339fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String { 2399fn 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
2407fn type_at(content: &str) -> String {
2408 let (db, file_pos) = MockDatabase::with_position(content);
2409 type_at_pos(&db, file_pos)
2410}
2411
2347fn infer(content: &str) -> String { 2412fn 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);
diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs
index 4a111aba5..e34ddf24a 100644
--- a/crates/ra_ide_api/src/completion/complete_dot.rs
+++ b/crates/ra_ide_api/src/completion/complete_dot.rs
@@ -37,7 +37,7 @@ fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty)
37} 37}
38 38
39fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) { 39fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) {
40 receiver.iterate_methods(ctx.db, |_ty, func| { 40 ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| {
41 let sig = func.signature(ctx.db); 41 let sig = func.signature(ctx.db);
42 if sig.has_self_param() { 42 if sig.has_self_param() {
43 acc.add_function(ctx, func); 43 acc.add_function(ctx, func);
@@ -196,6 +196,32 @@ mod tests {
196 } 196 }
197 197
198 #[test] 198 #[test]
199 fn test_trait_method_completion() {
200 assert_debug_snapshot_matches!(
201 do_ref_completion(
202 r"
203 struct A {}
204 trait Trait { fn the_method(&self); }
205 impl Trait for A {}
206 fn foo(a: A) {
207 a.<|>
208 }
209 ",
210 ),
211 @r###"[
212 CompletionItem {
213 label: "the_method",
214 source_range: [151; 151),
215 delete: [151; 151),
216 insert: "the_method()$0",
217 kind: Method,
218 detail: "fn the_method(&self)"
219 }
220]"###
221 );
222 }
223
224 #[test]
199 fn test_no_non_self_method() { 225 fn test_no_non_self_method() {
200 assert_debug_snapshot_matches!( 226 assert_debug_snapshot_matches!(
201 do_ref_completion( 227 do_ref_completion(