From ca49fbe0a1f6acc1352f6628c36bb7dfe3a950e5 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 28 May 2021 14:02:53 +0200 Subject: Complete `self.` prefixed fields and methods inside methods --- crates/ide_completion/src/completions.rs | 62 ++++++++++++++++++++-- crates/ide_completion/src/completions/dot.rs | 41 ++------------ crates/ide_completion/src/completions/record.rs | 2 +- .../src/completions/unqualified_path.rs | 48 +++++++++++++++++ crates/ide_completion/src/render.rs | 30 ++++++++--- crates/ide_completion/src/render/function.rs | 36 +++++++++++-- 6 files changed, 165 insertions(+), 54 deletions(-) (limited to 'crates/ide_completion') 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; use std::iter; -use hir::known; +use either::Either; +use hir::{known, HasVisibility}; use ide_db::SymbolKind; +use rustc_hash::FxHashSet; use crate::{ item::{Builder, CompletionKind}, @@ -69,18 +71,25 @@ impl Completions { items.into_iter().for_each(|item| self.add(item.into())) } - pub(crate) fn add_field(&mut self, ctx: &CompletionContext, field: hir::Field, ty: &hir::Type) { - let item = render_field(RenderContext::new(ctx), field, ty); + pub(crate) fn add_field( + &mut self, + ctx: &CompletionContext, + receiver: Option, + field: hir::Field, + ty: &hir::Type, + ) { + let item = render_field(RenderContext::new(ctx), receiver, field, ty); self.add(item); } pub(crate) fn add_tuple_field( &mut self, ctx: &CompletionContext, + receiver: Option, field: usize, ty: &hir::Type, ) { - let item = render_tuple_field(RenderContext::new(ctx), field, ty); + let item = render_tuple_field(RenderContext::new(ctx), receiver, field, ty); self.add(item); } @@ -132,9 +141,11 @@ impl Completions { &mut self, ctx: &CompletionContext, func: hir::Function, + receiver: Option, local_name: Option, ) { - if let Some(item) = render_method(RenderContext::new(ctx), None, local_name, func) { + if let Some(item) = render_method(RenderContext::new(ctx), None, receiver, local_name, func) + { self.add(item) } } @@ -243,3 +254,44 @@ fn complete_enum_variants( } } } + +fn complete_fields( + ctx: &CompletionContext, + receiver: &hir::Type, + mut f: impl FnMut(Either, hir::Type), +) { + for receiver in receiver.autoderef(ctx.db) { + for (field, ty) in receiver.fields(ctx.db) { + if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { + // Skip private field. FIXME: If the definition location of the + // field is editable, we should show the completion + continue; + } + f(Either::Left(field), ty); + } + for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { + // FIXME: Handle visibility + f(Either::Right(i), ty); + } + } +} + +fn complete_methods( + ctx: &CompletionContext, + receiver: &hir::Type, + mut f: impl FnMut(hir::Function), +) { + if let Some(krate) = ctx.krate { + let mut seen_methods = FxHashSet::default(); + let traits_in_scope = ctx.scope.traits_in_scope(); + receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { + if func.self_param(ctx.db).is_some() + && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) + && seen_methods.insert(func.name(ctx.db)) + { + f(func); + } + None::<()> + }); + } +} 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 @@ //! Completes references after dot (fields and method calls). -use hir::{HasVisibility, Type}; -use rustc_hash::FxHashSet; +use either::Either; use crate::{context::CompletionContext, Completions}; @@ -20,42 +19,12 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { if ctx.is_call { cov_mark::hit!(test_no_struct_field_completion_for_method_call); } else { - complete_fields(acc, ctx, &receiver_ty); - } - complete_methods(acc, ctx, &receiver_ty); -} - -fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { - for receiver in receiver.autoderef(ctx.db) { - for (field, ty) in receiver.fields(ctx.db) { - if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) { - // Skip private field. FIXME: If the definition location of the - // field is editable, we should show the completion - continue; - } - acc.add_field(ctx, field, &ty); - } - for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { - // FIXME: Handle visibility - acc.add_tuple_field(ctx, i, &ty); - } - } -} - -fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { - if let Some(krate) = ctx.krate { - let mut seen_methods = FxHashSet::default(); - let traits_in_scope = ctx.scope.traits_in_scope(); - receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| { - if func.self_param(ctx.db).is_some() - && ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m)) - && seen_methods.insert(func.name(ctx.db)) - { - acc.add_method(ctx, func, None); - } - None::<()> + super::complete_fields(ctx, &receiver_ty, |field, ty| match field { + Either::Left(field) => acc.add_field(ctx, None, field, &ty), + Either::Right(tuple_idx) => acc.add_tuple_field(ctx, None, tuple_idx, &ty), }); } + super::complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, func, None, None)); } #[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) -> }; for (field, ty) in missing_fields { - acc.add_field(ctx, field, &ty); + acc.add_field(ctx, None, field, &ty); } 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 if ctx.is_path_disallowed() || ctx.expects_item() { return; } + if ctx.expects_assoc_item() { ctx.scope.process_all_names(&mut |name, def| { if let ScopeDef::MacroDef(macro_def) = def { @@ -32,6 +33,7 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC }); return; } + if let Some(hir::Adt::Enum(e)) = ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { @@ -45,6 +47,22 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC cov_mark::hit!(skip_lifetime_completion); return; } + if let ScopeDef::Local(local) = &res { + if local.is_self(ctx.db) { + let ty = local.ty(ctx.db); + super::complete_fields(ctx, &ty, |field, ty| match field { + either::Either::Left(field) => { + acc.add_field(ctx, Some(name.to_string()), field, &ty) + } + either::Either::Right(tuple_idx) => { + acc.add_tuple_field(ctx, Some(name.to_string()), tuple_idx, &ty) + } + }); + super::complete_methods(ctx, &ty, |func| { + acc.add_method(ctx, func, Some(name.to_string()), None) + }); + } + } acc.add_resolution(ctx, name, &res); }); } @@ -375,6 +393,36 @@ fn foo() { ); } + #[test] + fn completes_qualified_fields_and_methods_in_methods() { + check( + r#" +struct Foo { field: i32 } + +impl Foo { fn foo(&self) { $0 } }"#, + expect![[r#" + fd self.field i32 + me self.foo() fn(&self) + lc self &Foo + sp Self + st Foo + "#]], + ); + check( + r#" +struct Foo(i32); + +impl Foo { fn foo(&mut self) { $0 } }"#, + expect![[r#" + fd self.0 i32 + me self.foo() fn(&mut self) + lc self &mut Foo + sp Self + st Foo + "#]], + ); + } + #[test] fn completes_prelude() { check( 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::{ pub(crate) fn render_field<'a>( ctx: RenderContext<'a>, + receiver: Option, field: hir::Field, ty: &hir::Type, ) -> CompletionItem { - Render::new(ctx).render_field(field, ty) + Render::new(ctx).render_field(receiver, field, ty) } pub(crate) fn render_tuple_field<'a>( ctx: RenderContext<'a>, + receiver: Option, field: usize, ty: &hir::Type, ) -> CompletionItem { - Render::new(ctx).render_tuple_field(field, ty) + Render::new(ctx).render_tuple_field(receiver, field, ty) } pub(crate) fn render_resolution<'a>( @@ -126,11 +128,19 @@ impl<'a> Render<'a> { Render { ctx } } - fn render_field(&self, field: hir::Field, ty: &hir::Type) -> CompletionItem { + fn render_field( + &self, + receiver: Option, + field: hir::Field, + ty: &hir::Type, + ) -> CompletionItem { let is_deprecated = self.ctx.is_deprecated(field); let name = field.name(self.ctx.db()).to_string(); - let mut item = - CompletionItem::new(CompletionKind::Reference, self.ctx.source_range(), name.clone()); + let mut item = CompletionItem::new( + CompletionKind::Reference, + self.ctx.source_range(), + receiver.map_or_else(|| name.to_string(), |receiver| format!("{}.{}", receiver, name)), + ); item.kind(SymbolKind::Field) .detail(ty.display(self.ctx.db()).to_string()) .set_documentation(field.docs(self.ctx.db())) @@ -151,11 +161,17 @@ impl<'a> Render<'a> { item.build() } - fn render_tuple_field(&self, field: usize, ty: &hir::Type) -> CompletionItem { + fn render_tuple_field( + &self, + receiver: Option, + field: usize, + ty: &hir::Type, + ) -> CompletionItem { let mut item = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), - field.to_string(), + receiver + .map_or_else(|| field.to_string(), |receiver| format!("{}.{}", receiver, field)), ); 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>( fn_: hir::Function, ) -> Option { let _p = profile::span("render_fn"); - Some(FunctionRender::new(ctx, local_name, fn_, false)?.render(import_to_add)) + Some(FunctionRender::new(ctx, None, local_name, fn_, false)?.render(import_to_add)) } pub(crate) fn render_method<'a>( ctx: RenderContext<'a>, import_to_add: Option, + receiver: Option, local_name: Option, fn_: hir::Function, ) -> Option { let _p = profile::span("render_method"); - Some(FunctionRender::new(ctx, local_name, fn_, true)?.render(import_to_add)) + Some(FunctionRender::new(ctx, receiver, local_name, fn_, true)?.render(import_to_add)) } #[derive(Debug)] struct FunctionRender<'a> { ctx: RenderContext<'a>, name: String, + receiver: Option, func: hir::Function, ast_node: Fn, is_method: bool, @@ -45,6 +47,7 @@ struct FunctionRender<'a> { impl<'a> FunctionRender<'a> { fn new( ctx: RenderContext<'a>, + receiver: Option, local_name: Option, fn_: hir::Function, is_method: bool, @@ -52,11 +55,14 @@ impl<'a> FunctionRender<'a> { let name = local_name.unwrap_or_else(|| fn_.name(ctx.db())).to_string(); let ast_node = fn_.source(ctx.db())?.value; - Some(FunctionRender { ctx, name, func: fn_, ast_node, is_method }) + Some(FunctionRender { ctx, name, receiver, func: fn_, ast_node, is_method }) } - fn render(self, import_to_add: Option) -> CompletionItem { + fn render(mut self, import_to_add: Option) -> CompletionItem { let params = self.params(); + if let Some(receiver) = &self.receiver { + self.name = format!("{}.{}", receiver, &self.name) + } let mut item = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), @@ -148,7 +154,7 @@ impl<'a> FunctionRender<'a> { }; let mut params_pats = Vec::new(); - let params_ty = if self.ctx.completion.dot_receiver.is_some() { + let params_ty = if self.ctx.completion.dot_receiver.is_some() || self.receiver.is_some() { self.func.method_params(self.ctx.db()).unwrap_or_default() } else { if let Some(s) = ast_params.self_param() { @@ -253,6 +259,26 @@ impl S { fn bar(s: &S) { s.foo(${1:x})$0 } +"#, + ); + + check_edit( + "self.foo", + r#" +struct S {} +impl S { + fn foo(&self, x: i32) { + $0 + } +} +"#, + r#" +struct S {} +impl S { + fn foo(&self, x: i32) { + self.foo(${1:x})$0 + } +} "#, ); } -- cgit v1.2.3