diff options
author | Lukas Wirth <[email protected]> | 2021-05-28 13:02:53 +0100 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-05-31 13:52:55 +0100 |
commit | ca49fbe0a1f6acc1352f6628c36bb7dfe3a950e5 (patch) | |
tree | ce49d1f51186dd6f455bccadb73a577988ff457a | |
parent | d7cbb49057c4495307d91f5db32465c29c175124 (diff) |
Complete `self.` prefixed fields and methods inside methods
-rw-r--r-- | crates/ide_completion/src/completions.rs | 62 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/dot.rs | 41 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/record.rs | 2 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/unqualified_path.rs | 48 | ||||
-rw-r--r-- | crates/ide_completion/src/render.rs | 30 | ||||
-rw-r--r-- | crates/ide_completion/src/render/function.rs | 36 |
6 files changed, 165 insertions, 54 deletions
diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 151bf3783..0f0553a65 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs | |||
@@ -18,8 +18,10 @@ pub(crate) mod unqualified_path; | |||
18 | 18 | ||
19 | use std::iter; | 19 | use std::iter; |
20 | 20 | ||
21 | use hir::known; | 21 | use either::Either; |
22 | use hir::{known, HasVisibility}; | ||
22 | use ide_db::SymbolKind; | 23 | use ide_db::SymbolKind; |
24 | use rustc_hash::FxHashSet; | ||
23 | 25 | ||
24 | use crate::{ | 26 | use crate::{ |
25 | item::{Builder, CompletionKind}, | 27 | item::{Builder, CompletionKind}, |
@@ -69,18 +71,25 @@ impl Completions { | |||
69 | items.into_iter().for_each(|item| self.add(item.into())) | 71 | items.into_iter().for_each(|item| self.add(item.into())) |
70 | } | 72 | } |
71 | 73 | ||
72 | pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &hir::Type) { | 74 | pub(crate) fn add_field( |
73 | let item = render_field(RenderContext::new(ctx), field, ty); | 75 | &mut self, |
76 | ctx: &CompletionContext, | ||
77 | receiver: Option<String>, | ||
78 | field: hir::Field, | ||
79 | ty: &hir::Type, | ||
80 | ) { | ||
81 | let item = render_field(RenderContext::new(ctx), receiver, field, ty); | ||
74 | self.add(item); | 82 | self.add(item); |
75 | } | 83 | } |
76 | 84 | ||
77 | pub(crate) fn add_tuple_field( | 85 | pub(crate) fn add_tuple_field( |
78 | &mut self, | 86 | &mut self, |
79 | ctx: &CompletionContext, | 87 | ctx: &CompletionContext, |
88 | receiver: Option<String>, | ||
80 | field: usize, | 89 | field: usize, |
81 | ty: &hir::Type, | 90 | ty: &hir::Type, |
82 | ) { | 91 | ) { |
83 | let item = render_tuple_field(RenderContext::new(ctx), field, ty); | 92 | let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); |
84 | self.add(item); | 93 | self.add(item); |
85 | } | 94 | } |
86 | 95 | ||
@@ -132,9 +141,11 @@ impl Completions { | |||
132 | &mut self, | 141 | &mut self, |
133 | ctx: &CompletionContext, | 142 | ctx: &CompletionContext, |
134 | func: hir::Function, | 143 | func: hir::Function, |
144 | receiver: Option<String>, | ||
135 | local_name: Option<hir::Name>, | 145 | local_name: Option<hir::Name>, |
136 | ) { | 146 | ) { |
137 | if let Some(item) = render_method(RenderContext::new(ctx), None, local_name, func) { | 147 | if let Some(item) = render_method(RenderContext::new(ctx), None, receiver, local_name, func) |
148 | { | ||
138 | self.add(item) | 149 | self.add(item) |
139 | } | 150 | } |
140 | } | 151 | } |
@@ -243,3 +254,44 @@ fn complete_enum_variants( | |||
243 | } | 254 | } |
244 | } | 255 | } |
245 | } | 256 | } |
257 | |||
258 | fn complete_fields( | ||
259 | ctx: &CompletionContext, | ||
260 | receiver: &hir::Type, | ||
261 | mut f: impl FnMut(Either<hir::Field, usize>, hir::Type), | ||
262 | ) { | ||
263 | for receiver in receiver.autoderef(ctx.db) { | ||
264 | for (field, ty) in receiver.fields(ctx.db) { | ||
265 | if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { | ||
266 | // Skip private field. FIXME: If the definition location of the | ||
267 | // field is editable, we should show the completion | ||
268 | continue; | ||
269 | } | ||
270 | f(Either::Left(field), ty); | ||
271 | } | ||
272 | for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { | ||
273 | // FIXME: Handle visibility | ||
274 | f(Either::Right(i), ty); | ||
275 | } | ||
276 | } | ||
277 | } | ||
278 | |||
279 | fn complete_methods( | ||
280 | ctx: &CompletionContext, | ||
281 | receiver: &hir::Type, | ||
282 | mut f: impl FnMut(hir::Function), | ||
283 | ) { | ||
284 | if let Some(krate) = ctx.krate { | ||
285 | let mut seen_methods = FxHashSet::default(); | ||
286 | let traits_in_scope = ctx.scope.traits_in_scope(); | ||
287 | receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { | ||
288 | if func.self_param(ctx.db).is_some() | ||
289 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) | ||
290 | && seen_methods.insert(func.name(ctx.db)) | ||
291 | { | ||
292 | f(func); | ||
293 | } | ||
294 | None::<()> | ||
295 | }); | ||
296 | } | ||
297 | } | ||
diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index fd9738743..93f7bd6d4 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs | |||
@@ -1,7 +1,6 @@ | |||
1 | //! Completes references after dot (fields and method calls). | 1 | //! Completes references after dot (fields and method calls). |
2 | 2 | ||
3 | use hir::{HasVisibility, Type}; | 3 | use either::Either; |
4 | use rustc_hash::FxHashSet; | ||
5 | 4 | ||
6 | use crate::{context::CompletionContext, Completions}; | 5 | use crate::{context::CompletionContext, Completions}; |
7 | 6 | ||
@@ -20,42 +19,12 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { | |||
20 | if ctx.is_call { | 19 | if ctx.is_call { |
21 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); | 20 | cov_mark::hit!(test_no_struct_field_completion_for_method_call); |
22 | } else { | 21 | } else { |
23 | complete_fields(acc, ctx, &receiver_ty); | 22 | super::complete_fields(ctx, &receiver_ty, |field, ty| match field { |
24 | } | 23 | Either::Left(field) => acc.add_field(ctx, None, field, &ty), |
25 | complete_methods(acc, ctx, &receiver_ty); | 24 | Either::Right(tuple_idx) => acc.add_tuple_field(ctx, None, tuple_idx, &ty), |
26 | } | ||
27 | |||
28 | fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | ||
29 | for receiver in receiver.autoderef(ctx.db) { | ||
30 | for (field, ty) in receiver.fields(ctx.db) { | ||
31 | if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { | ||
32 | // Skip private field. FIXME: If the definition location of the | ||
33 | // field is editable, we should show the completion | ||
34 | continue; | ||
35 | } | ||
36 | acc.add_field(ctx, field, &ty); | ||
37 | } | ||
38 | for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { | ||
39 | // FIXME: Handle visibility | ||
40 | acc.add_tuple_field(ctx, i, &ty); | ||
41 | } | ||
42 | } | ||
43 | } | ||
44 | |||
45 | fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { | ||
46 | if let Some(krate) = ctx.krate { | ||
47 | let mut seen_methods = FxHashSet::default(); | ||
48 | let traits_in_scope = ctx.scope.traits_in_scope(); | ||
49 | receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { | ||
50 | if func.self_param(ctx.db).is_some() | ||
51 | && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) | ||
52 | && seen_methods.insert(func.name(ctx.db)) | ||
53 | { | ||
54 | acc.add_method(ctx, func, None); | ||
55 | } | ||
56 | None::<()> | ||
57 | }); | 25 | }); |
58 | } | 26 | } |
27 | super::complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, func, None, None)); | ||
59 | } | 28 | } |
60 | 29 | ||
61 | #[cfg(test)] | 30 | #[cfg(test)] |
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs index 227c08d01..0ac47cdbe 100644 --- a/crates/ide_completion/src/completions/record.rs +++ b/crates/ide_completion/src/completions/record.rs | |||
@@ -39,7 +39,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> | |||
39 | }; | 39 | }; |
40 | 40 | ||
41 | for (field, ty) in missing_fields { | 41 | for (field, ty) in missing_fields { |
42 | acc.add_field(ctx, field, &ty); | 42 | acc.add_field(ctx, None, field, &ty); |
43 | } | 43 | } |
44 | 44 | ||
45 | Some(()) | 45 | Some(()) |
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index 9db8516d0..573a39996 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs | |||
@@ -11,6 +11,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
11 | if ctx.is_path_disallowed() || ctx.expects_item() { | 11 | if ctx.is_path_disallowed() || ctx.expects_item() { |
12 | return; | 12 | return; |
13 | } | 13 | } |
14 | |||
14 | if ctx.expects_assoc_item() { | 15 | if ctx.expects_assoc_item() { |
15 | ctx.scope.process_all_names(&mut |name, def| { | 16 | ctx.scope.process_all_names(&mut |name, def| { |
16 | if let ScopeDef::MacroDef(macro_def) = def { | 17 | if let ScopeDef::MacroDef(macro_def) = def { |
@@ -32,6 +33,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
32 | }); | 33 | }); |
33 | return; | 34 | return; |
34 | } | 35 | } |
36 | |||
35 | if let Some(hir::Adt::Enum(e)) = | 37 | if let Some(hir::Adt::Enum(e)) = |
36 | ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) | 38 | ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) |
37 | { | 39 | { |
@@ -45,6 +47,22 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC | |||
45 | cov_mark::hit!(skip_lifetime_completion); | 47 | cov_mark::hit!(skip_lifetime_completion); |
46 | return; | 48 | return; |
47 | } | 49 | } |
50 | if let ScopeDef::Local(local) = &res { | ||
51 | if local.is_self(ctx.db) { | ||
52 | let ty = local.ty(ctx.db); | ||
53 | super::complete_fields(ctx, &ty, |field, ty| match field { | ||
54 | either::Either::Left(field) => { | ||
55 | acc.add_field(ctx, Some(name.to_string()), field, &ty) | ||
56 | } | ||
57 | either::Either::Right(tuple_idx) => { | ||
58 | acc.add_tuple_field(ctx, Some(name.to_string()), tuple_idx, &ty) | ||
59 | } | ||
60 | }); | ||
61 | super::complete_methods(ctx, &ty, |func| { | ||
62 | acc.add_method(ctx, func, Some(name.to_string()), None) | ||
63 | }); | ||
64 | } | ||
65 | } | ||
48 | acc.add_resolution(ctx, name, &res); | 66 | acc.add_resolution(ctx, name, &res); |
49 | }); | 67 | }); |
50 | } | 68 | } |
@@ -376,6 +394,36 @@ fn foo() { | |||
376 | } | 394 | } |
377 | 395 | ||
378 | #[test] | 396 | #[test] |
397 | fn completes_qualified_fields_and_methods_in_methods() { | ||
398 | check( | ||
399 | r#" | ||
400 | struct Foo { field: i32 } | ||
401 | |||
402 | impl Foo { fn foo(&self) { $0 } }"#, | ||
403 | expect![[r#" | ||
404 | fd self.field i32 | ||
405 | me self.foo() fn(&self) | ||
406 | lc self &Foo | ||
407 | sp Self | ||
408 | st Foo | ||
409 | "#]], | ||
410 | ); | ||
411 | check( | ||
412 | r#" | ||
413 | struct Foo(i32); | ||
414 | |||
415 | impl Foo { fn foo(&mut self) { $0 } }"#, | ||
416 | expect![[r#" | ||
417 | fd self.0 i32 | ||
418 | me self.foo() fn(&mut self) | ||
419 | lc self &mut Foo | ||
420 | sp Self | ||
421 | st Foo | ||
422 | "#]], | ||
423 | ); | ||
424 | } | ||
425 | |||
426 | #[test] | ||
379 | fn completes_prelude() { | 427 | fn completes_prelude() { |
380 | check( | 428 | check( |
381 | r#" | 429 | r#" |
diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 425dd0247..bf59ff57b 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs | |||
@@ -25,18 +25,20 @@ use crate::{ | |||
25 | 25 | ||
26 | pub(crate) fn render_field<'a>( | 26 | pub(crate) fn render_field<'a>( |
27 | ctx: RenderContext<'a>, | 27 | ctx: RenderContext<'a>, |
28 | receiver: Option<String>, | ||
28 | field: hir::Field, | 29 | field: hir::Field, |
29 | ty: &hir::Type, | 30 | ty: &hir::Type, |
30 | ) -> CompletionItem { | 31 | ) -> CompletionItem { |
31 | Render::new(ctx).render_field(field, ty) | 32 | Render::new(ctx).render_field(receiver, field, ty) |
32 | } | 33 | } |
33 | 34 | ||
34 | pub(crate) fn render_tuple_field<'a>( | 35 | pub(crate) fn render_tuple_field<'a>( |
35 | ctx: RenderContext<'a>, | 36 | ctx: RenderContext<'a>, |
37 | receiver: Option<String>, | ||
36 | field: usize, | 38 | field: usize, |
37 | ty: &hir::Type, | 39 | ty: &hir::Type, |
38 | ) -> CompletionItem { | 40 | ) -> CompletionItem { |
39 | Render::new(ctx).render_tuple_field(field, ty) | 41 | Render::new(ctx).render_tuple_field(receiver, field, ty) |
40 | } | 42 | } |
41 | 43 | ||
42 | pub(crate) fn render_resolution<'a>( | 44 | pub(crate) fn render_resolution<'a>( |
@@ -126,11 +128,19 @@ impl<'a> Render<'a> { | |||
126 | Render { ctx } | 128 | Render { ctx } |
127 | } | 129 | } |
128 | 130 | ||
129 | fn render_field(&self, field: hir::Field, ty: &hir::Type) -> CompletionItem { | 131 | fn render_field( |
132 | &self, | ||
133 | receiver: Option<String>, | ||
134 | field: hir::Field, | ||
135 | ty: &hir::Type, | ||
136 | ) -> CompletionItem { | ||
130 | let is_deprecated = self.ctx.is_deprecated(field); | 137 | let is_deprecated = self.ctx.is_deprecated(field); |
131 | let name = field.name(self.ctx.db()).to_string(); | 138 | let name = field.name(self.ctx.db()).to_string(); |
132 | let mut item = | 139 | let mut item = CompletionItem::new( |
133 | CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name.clone()); | 140 | CompletionKind::Reference, |
141 | self.ctx.source_range(), | ||
142 | receiver.map_or_else(|| name.to_string(), |receiver| format!("{}.{}", receiver, name)), | ||
143 | ); | ||
134 | item.kind(SymbolKind::Field) | 144 | item.kind(SymbolKind::Field) |
135 | .detail(ty.display(self.ctx.db()).to_string()) | 145 | .detail(ty.display(self.ctx.db()).to_string()) |
136 | .set_documentation(field.docs(self.ctx.db())) | 146 | .set_documentation(field.docs(self.ctx.db())) |
@@ -151,11 +161,17 @@ impl<'a> Render<'a> { | |||
151 | item.build() | 161 | item.build() |
152 | } | 162 | } |
153 | 163 | ||
154 | fn render_tuple_field(&self, field: usize, ty: &hir::Type) -> CompletionItem { | 164 | fn render_tuple_field( |
165 | &self, | ||
166 | receiver: Option<String>, | ||
167 | field: usize, | ||
168 | ty: &hir::Type, | ||
169 | ) -> CompletionItem { | ||
155 | let mut item = CompletionItem::new( | 170 | let mut item = CompletionItem::new( |
156 | CompletionKind::Reference, | 171 | CompletionKind::Reference, |
157 | self.ctx.source_range(), | 172 | self.ctx.source_range(), |
158 | field.to_string(), | 173 | receiver |
174 | .map_or_else(|| field.to_string(), |receiver| format!("{}.{}", receiver, field)), | ||
159 | ); | 175 | ); |
160 | 176 | ||
161 | item.kind(SymbolKind::Field).detail(ty.display(self.ctx.db()).to_string()); | 177 | item.kind(SymbolKind::Field).detail(ty.display(self.ctx.db()).to_string()); |
diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index 63bd66926..b3ba6114d 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs | |||
@@ -20,23 +20,25 @@ pub(crate) fn render_fn<'a>( | |||
20 | fn_: hir::Function, | 20 | fn_: hir::Function, |
21 | ) -> Option<CompletionItem> { | 21 | ) -> Option<CompletionItem> { |
22 | let _p = profile::span("render_fn"); | 22 | let _p = profile::span("render_fn"); |
23 | Some(FunctionRender::new(ctx, local_name, fn_, false)?.render(import_to_add)) | 23 | Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add)) |
24 | } | 24 | } |
25 | 25 | ||
26 | pub(crate) fn render_method<'a>( | 26 | pub(crate) fn render_method<'a>( |
27 | ctx: RenderContext<'a>, | 27 | ctx: RenderContext<'a>, |
28 | import_to_add: Option<ImportEdit>, | 28 | import_to_add: Option<ImportEdit>, |
29 | receiver: Option<String>, | ||
29 | local_name: Option<hir::Name>, | 30 | local_name: Option<hir::Name>, |
30 | fn_: hir::Function, | 31 | fn_: hir::Function, |
31 | ) -> Option<CompletionItem> { | 32 | ) -> Option<CompletionItem> { |
32 | let _p = profile::span("render_method"); | 33 | let _p = profile::span("render_method"); |
33 | Some(FunctionRender::new(ctx, local_name, fn_, true)?.render(import_to_add)) | 34 | Some(FunctionRender::new(ctx, receiver, local_name, fn_, true)?.render(import_to_add)) |
34 | } | 35 | } |
35 | 36 | ||
36 | #[derive(Debug)] | 37 | #[derive(Debug)] |
37 | struct FunctionRender<'a> { | 38 | struct FunctionRender<'a> { |
38 | ctx: RenderContext<'a>, | 39 | ctx: RenderContext<'a>, |
39 | name: String, | 40 | name: String, |
41 | receiver: Option<String>, | ||
40 | func: hir::Function, | 42 | func: hir::Function, |
41 | ast_node: Fn, | 43 | ast_node: Fn, |
42 | is_method: bool, | 44 | is_method: bool, |
@@ -45,6 +47,7 @@ struct FunctionRender<'a> { | |||
45 | impl<'a> FunctionRender<'a> { | 47 | impl<'a> FunctionRender<'a> { |
46 | fn new( | 48 | fn new( |
47 | ctx: RenderContext<'a>, | 49 | ctx: RenderContext<'a>, |
50 | receiver: Option<String>, | ||
48 | local_name: Option<hir::Name>, | 51 | local_name: Option<hir::Name>, |
49 | fn_: hir::Function, | 52 | fn_: hir::Function, |
50 | is_method: bool, | 53 | is_method: bool, |
@@ -52,11 +55,14 @@ impl<'a> FunctionRender<'a> { | |||
52 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string(); | 55 | let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string(); |
53 | let ast_node = fn_.source(ctx.db())?.value; | 56 | let ast_node = fn_.source(ctx.db())?.value; |
54 | 57 | ||
55 | Some(FunctionRender { ctx, name, func: fn_, ast_node, is_method }) | 58 | Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method }) |
56 | } | 59 | } |
57 | 60 | ||
58 | fn render(self, import_to_add: Option<ImportEdit>) -> CompletionItem { | 61 | fn render(mut self, import_to_add: Option<ImportEdit>) -> CompletionItem { |
59 | let params = self.params(); | 62 | let params = self.params(); |
63 | if let Some(receiver) = &self.receiver { | ||
64 | self.name = format!("{}.{}", receiver, &self.name) | ||
65 | } | ||
60 | let mut item = CompletionItem::new( | 66 | let mut item = CompletionItem::new( |
61 | CompletionKind::Reference, | 67 | CompletionKind::Reference, |
62 | self.ctx.source_range(), | 68 | self.ctx.source_range(), |
@@ -148,7 +154,7 @@ impl<'a> FunctionRender<'a> { | |||
148 | }; | 154 | }; |
149 | 155 | ||
150 | let mut params_pats = Vec::new(); | 156 | let mut params_pats = Vec::new(); |
151 | let params_ty = if self.ctx.completion.dot_receiver.is_some() { | 157 | let params_ty = if self.ctx.completion.dot_receiver.is_some() || self.receiver.is_some() { |
152 | self.func.method_params(self.ctx.db()).unwrap_or_default() | 158 | self.func.method_params(self.ctx.db()).unwrap_or_default() |
153 | } else { | 159 | } else { |
154 | if let Some(s) = ast_params.self_param() { | 160 | if let Some(s) = ast_params.self_param() { |
@@ -255,6 +261,26 @@ fn bar(s: &S) { | |||
255 | } | 261 | } |
256 | "#, | 262 | "#, |
257 | ); | 263 | ); |
264 | |||
265 | check_edit( | ||
266 | "self.foo", | ||
267 | r#" | ||
268 | struct S {} | ||
269 | impl S { | ||
270 | fn foo(&self, x: i32) { | ||
271 | $0 | ||
272 | } | ||
273 | } | ||
274 | "#, | ||
275 | r#" | ||
276 | struct S {} | ||
277 | impl S { | ||
278 | fn foo(&self, x: i32) { | ||
279 | self.foo(${1:x})$0 | ||
280 | } | ||
281 | } | ||
282 | "#, | ||
283 | ); | ||
258 | } | 284 | } |
259 | 285 | ||
260 | #[test] | 286 | #[test] |