From ca49fbe0a1f6acc1352f6628c36bb7dfe3a950e5 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 28 May 2021 14:02:53 +0200 Subject: [PATCH 1/4] Complete `self.` prefixed fields and methods inside methods --- crates/ide_completion/src/completions.rs | 62 +++++++++++++++++-- crates/ide_completion/src/completions/dot.rs | 41 ++---------- .../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(-) diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 151bf378390..0f0553a65c7 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -18,8 +18,10 @@ 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 @@ pub(crate) fn add_all(&mut self, items: I) 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 @@ pub(crate) fn add_method( &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 fd97387437b..93f7bd6d4e3 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 227c08d0107..0ac47cdbe7e 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 9db8516d053..573a399964f 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 completes_self_in_methods() { ); } + #[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 425dd0247c8..bf59ff57b1b 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -25,18 +25,20 @@ 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 @@ fn new(ctx: RenderContext<'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 @@ fn render_field(&self, field: hir::Field, ty: &hir::Type) -> CompletionItem { 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 63bd6692601..b3ba6114d91 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 @@ fn new( 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 @@ fn params(&self) -> Params { }; 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 @@ fn foo(&self, x: i32) {} 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 + } +} "#, ); } From d346f5bf75bfe3c7dc357c748c257569c0fb23c3 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 28 May 2021 14:38:09 +0200 Subject: [PATCH 2/4] Less strings, more hir::Names --- crates/ide_completion/src/completions.rs | 6 +++--- crates/ide_completion/src/completions/unqualified_path.rs | 6 +++--- crates/ide_completion/src/render.rs | 8 ++++---- crates/ide_completion/src/render/function.rs | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index 0f0553a65c7..dd92bc51097 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -74,7 +74,7 @@ pub(crate) fn add_all(&mut self, items: I) pub(crate) fn add_field( &mut self, ctx: &CompletionContext, - receiver: Option, + receiver: Option, field: hir::Field, ty: &hir::Type, ) { @@ -85,7 +85,7 @@ pub(crate) fn add_field( pub(crate) fn add_tuple_field( &mut self, ctx: &CompletionContext, - receiver: Option, + receiver: Option, field: usize, ty: &hir::Type, ) { @@ -141,7 +141,7 @@ pub(crate) fn add_method( &mut self, ctx: &CompletionContext, func: hir::Function, - receiver: Option, + receiver: Option, local_name: Option, ) { if let Some(item) = render_method(RenderContext::new(ctx), None, receiver, local_name, func) diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index 573a399964f..83cb671010b 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -52,14 +52,14 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC 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) + acc.add_field(ctx, Some(name.clone()), field, &ty) } either::Either::Right(tuple_idx) => { - acc.add_tuple_field(ctx, Some(name.to_string()), tuple_idx, &ty) + acc.add_tuple_field(ctx, Some(name.clone()), tuple_idx, &ty) } }); super::complete_methods(ctx, &ty, |func| { - acc.add_method(ctx, func, Some(name.to_string()), None) + acc.add_method(ctx, func, Some(name.clone()), None) }); } } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index bf59ff57b1b..97dd528515c 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -25,7 +25,7 @@ pub(crate) fn render_field<'a>( ctx: RenderContext<'a>, - receiver: Option, + receiver: Option, field: hir::Field, ty: &hir::Type, ) -> CompletionItem { @@ -34,7 +34,7 @@ pub(crate) fn render_field<'a>( pub(crate) fn render_tuple_field<'a>( ctx: RenderContext<'a>, - receiver: Option, + receiver: Option, field: usize, ty: &hir::Type, ) -> CompletionItem { @@ -130,7 +130,7 @@ fn new(ctx: RenderContext<'a>) -> Render<'a> { fn render_field( &self, - receiver: Option, + receiver: Option, field: hir::Field, ty: &hir::Type, ) -> CompletionItem { @@ -163,7 +163,7 @@ fn render_field( fn render_tuple_field( &self, - receiver: Option, + receiver: Option, field: usize, ty: &hir::Type, ) -> CompletionItem { diff --git a/crates/ide_completion/src/render/function.rs b/crates/ide_completion/src/render/function.rs index b3ba6114d91..3ec77ca0f4a 100644 --- a/crates/ide_completion/src/render/function.rs +++ b/crates/ide_completion/src/render/function.rs @@ -26,7 +26,7 @@ pub(crate) fn render_fn<'a>( pub(crate) fn render_method<'a>( ctx: RenderContext<'a>, import_to_add: Option, - receiver: Option, + receiver: Option, local_name: Option, fn_: hir::Function, ) -> Option { @@ -38,7 +38,7 @@ pub(crate) fn render_method<'a>( struct FunctionRender<'a> { ctx: RenderContext<'a>, name: String, - receiver: Option, + receiver: Option, func: hir::Function, ast_node: Fn, is_method: bool, @@ -47,7 +47,7 @@ struct FunctionRender<'a> { impl<'a> FunctionRender<'a> { fn new( ctx: RenderContext<'a>, - receiver: Option, + receiver: Option, local_name: Option, fn_: hir::Function, is_method: bool, From 4507382f2e66cd0e6498228bfdffb16769063b0f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 28 May 2021 15:09:10 +0200 Subject: [PATCH 3/4] Move unprefixed field/method completion to `dot` --- crates/ide_completion/src/completions.rs | 45 +------- crates/ide_completion/src/completions/dot.rs | 103 +++++++++++++++++- .../src/completions/unqualified_path.rs | 46 -------- 3 files changed, 101 insertions(+), 93 deletions(-) diff --git a/crates/ide_completion/src/completions.rs b/crates/ide_completion/src/completions.rs index dd92bc51097..ffdcdc930a9 100644 --- a/crates/ide_completion/src/completions.rs +++ b/crates/ide_completion/src/completions.rs @@ -18,10 +18,8 @@ use std::iter; -use either::Either; -use hir::{known, HasVisibility}; +use hir::known; use ide_db::SymbolKind; -use rustc_hash::FxHashSet; use crate::{ item::{Builder, CompletionKind}, @@ -254,44 +252,3 @@ 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 93f7bd6d4e3..886251639e7 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs @@ -1,6 +1,8 @@ //! Completes references after dot (fields and method calls). use either::Either; +use hir::{HasVisibility, ScopeDef}; +use rustc_hash::FxHashSet; use crate::{context::CompletionContext, Completions}; @@ -8,7 +10,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { let dot_receiver = match &ctx.dot_receiver { Some(expr) => expr, - _ => return, + _ => return complete_undotted_self(acc, ctx), }; let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { @@ -19,12 +21,77 @@ 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 { - super::complete_fields(ctx, &receiver_ty, |field, ty| match field { + 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)); + complete_methods(ctx, &receiver_ty, |func| acc.add_method(ctx, func, None, None)); +} + +fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_trivial_path { + return; + } + ctx.scope.process_all_names(&mut |name, def| { + if let ScopeDef::Local(local) = &def { + if local.is_self(ctx.db) { + let ty = local.ty(ctx.db); + complete_fields(ctx, &ty, |field, ty| match field { + either::Either::Left(field) => { + acc.add_field(ctx, Some(name.clone()), field, &ty) + } + either::Either::Right(tuple_idx) => { + acc.add_tuple_field(ctx, Some(name.clone()), tuple_idx, &ty) + } + }); + complete_methods(ctx, &ty, |func| { + acc.add_method(ctx, func, Some(name.clone()), None) + }); + } + } + }); +} + +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::<()> + }); + } } #[cfg(test)] @@ -453,4 +520,34 @@ fn f(&mut self, v: Foo) { "#]], ); } + + #[test] + fn completes_bare_fields_and_methods_in_methods() { + check( + r#" +struct Foo { field: i32 } + +impl Foo { fn foo(&self) { $0 } }"#, + expect![[r#" + lc self &Foo + sp Self + st Foo + fd self.field i32 + me self.foo() fn(&self) + "#]], + ); + check( + r#" +struct Foo(i32); + +impl Foo { fn foo(&mut self) { $0 } }"#, + expect![[r#" + lc self &mut Foo + sp Self + st Foo + fd self.0 i32 + me self.foo() fn(&mut self) + "#]], + ); + } } diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs index 83cb671010b..20188a7ddc6 100644 --- a/crates/ide_completion/src/completions/unqualified_path.rs +++ b/crates/ide_completion/src/completions/unqualified_path.rs @@ -47,22 +47,6 @@ 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.clone()), field, &ty) - } - either::Either::Right(tuple_idx) => { - acc.add_tuple_field(ctx, Some(name.clone()), tuple_idx, &ty) - } - }); - super::complete_methods(ctx, &ty, |func| { - acc.add_method(ctx, func, Some(name.clone()), None) - }); - } - } acc.add_resolution(ctx, name, &res); }); } @@ -393,36 +377,6 @@ fn completes_self_in_methods() { ); } - #[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( From fb7105a5801ab1d0ede830cd53bbc3ccbf0b5e2c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 30 May 2021 16:41:33 +0200 Subject: [PATCH 4/4] Add config setting for self-on-the-fly --- crates/ide_completion/src/completions/dot.rs | 2 +- crates/ide_completion/src/config.rs | 1 + crates/ide_completion/src/render.rs | 2 +- crates/ide_completion/src/test_utils.rs | 1 + crates/rust-analyzer/src/config.rs | 4 ++++ crates/rust-analyzer/src/integrated_benchmarks.rs | 2 ++ crates/rust-analyzer/src/to_proto.rs | 1 + docs/dev/architecture.md | 5 +++++ docs/user/generated_config.adoc | 6 ++++++ editors/code/package.json | 5 +++++ 10 files changed, 27 insertions(+), 2 deletions(-) diff --git a/crates/ide_completion/src/completions/dot.rs b/crates/ide_completion/src/completions/dot.rs index 886251639e7..302c9ccbd36 100644 --- a/crates/ide_completion/src/completions/dot.rs +++ b/crates/ide_completion/src/completions/dot.rs @@ -30,7 +30,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { } fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { + if !ctx.is_trivial_path || !ctx.config.enable_self_on_the_fly { return; } ctx.scope.process_all_names(&mut |name, def| { diff --git a/crates/ide_completion/src/config.rs b/crates/ide_completion/src/config.rs index d70ed6c1cde..c300ce887be 100644 --- a/crates/ide_completion/src/config.rs +++ b/crates/ide_completion/src/config.rs @@ -10,6 +10,7 @@ pub struct CompletionConfig { pub enable_postfix_completions: bool, pub enable_imports_on_the_fly: bool, + pub enable_self_on_the_fly: bool, pub add_call_parenthesis: bool, pub add_call_argument_snippets: bool, pub snippet_cap: Option, diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 97dd528515c..a49a6071127 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -139,7 +139,7 @@ fn render_field( let mut item = CompletionItem::new( CompletionKind::Reference, self.ctx.source_range(), - receiver.map_or_else(|| name.to_string(), |receiver| format!("{}.{}", receiver, name)), + receiver.map_or_else(|| name.clone(), |receiver| format!("{}.{}", receiver, name)), ); item.kind(SymbolKind::Field) .detail(ty.display(self.ctx.db()).to_string()) diff --git a/crates/ide_completion/src/test_utils.rs b/crates/ide_completion/src/test_utils.rs index 93c7c872ccf..b0a4b2026b0 100644 --- a/crates/ide_completion/src/test_utils.rs +++ b/crates/ide_completion/src/test_utils.rs @@ -19,6 +19,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig { enable_postfix_completions: true, enable_imports_on_the_fly: true, + enable_self_on_the_fly: true, add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: SnippetCap::new(true), diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index a67b0bb2574..ae78fd4f612 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -100,6 +100,9 @@ struct ConfigData { /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. completion_autoimport_enable: bool = "true", + /// Toggles the additional completions that automatically show method calls and field accesses + /// with `self` prefixed to them when inside a method. + completion_autoself_enable: bool = "true", /// Whether to show native rust-analyzer diagnostics. diagnostics_enable: bool = "true", @@ -666,6 +669,7 @@ pub fn completion(&self) -> CompletionConfig { enable_postfix_completions: self.data.completion_postfix_enable, enable_imports_on_the_fly: self.data.completion_autoimport_enable && completion_item_edit_resolve(&self.caps), + enable_self_on_the_fly: self.data.completion_autoself_enable, add_call_parenthesis: self.data.completion_addCallParenthesis, add_call_argument_snippets: self.data.completion_addCallArgumentSnippets, insert_use: self.insert_use_config(), diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs index 781073fe5b8..ec36a5f5c00 100644 --- a/crates/rust-analyzer/src/integrated_benchmarks.rs +++ b/crates/rust-analyzer/src/integrated_benchmarks.rs @@ -132,6 +132,7 @@ fn integrated_completion_benchmark() { let config = CompletionConfig { enable_postfix_completions: true, enable_imports_on_the_fly: true, + enable_self_on_the_fly: true, add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: SnippetCap::new(true), @@ -166,6 +167,7 @@ fn integrated_completion_benchmark() { let config = CompletionConfig { enable_postfix_completions: true, enable_imports_on_the_fly: true, + enable_self_on_the_fly: true, add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: SnippetCap::new(true), diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index f5c8535a294..2b2ef2c60c3 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -1178,6 +1178,7 @@ fn main() { &ide::CompletionConfig { enable_postfix_completions: true, enable_imports_on_the_fly: true, + enable_self_on_the_fly: true, add_call_parenthesis: true, add_call_argument_snippets: true, snippet_cap: SnippetCap::new(true), diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 39edf9e19c6..2624069a5cc 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -447,3 +447,8 @@ This is cheap enough to enable in production. Similarly, we save live object counting (`RA_COUNT=1`). It is not cheap enough to enable in prod, and this is a bug which should be fixed. + +### Configurability + +rust-analyzer strives to be as configurable as possible while offering reasonable defaults where no configuration exists yet. +There will always be features that some people find more annoying than helpful, so giving the users the ability to tweak or disable these is a big part of offering a good user experience. diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 4a5782a57e0..dbd9a3503b5 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc @@ -136,6 +136,12 @@ Whether to show postfix snippets like `dbg`, `if`, `not`, etc. Toggles the additional completions that automatically add imports when completed. Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. -- +[[rust-analyzer.completion.autoself.enable]]rust-analyzer.completion.autoself.enable (default: `true`):: ++ +-- +Toggles the additional completions that automatically show method calls and field accesses +with `self` prefixed to them when inside a method. +-- [[rust-analyzer.diagnostics.enable]]rust-analyzer.diagnostics.enable (default: `true`):: + -- diff --git a/editors/code/package.json b/editors/code/package.json index 5b80cc1f9d9..42a06e13744 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -572,6 +572,11 @@ "default": true, "type": "boolean" }, + "rust-analyzer.completion.autoself.enable": { + "markdownDescription": "Toggles the additional completions that automatically show method calls and field accesses\nwith `self` prefixed to them when inside a method.", + "default": true, + "type": "boolean" + }, "rust-analyzer.diagnostics.enable": { "markdownDescription": "Whether to show native rust-analyzer diagnostics.", "default": true,