diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index 9c2e0dcf1c62..18cfa53f8e96 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -4,6 +4,7 @@ use std::ops::ControlFlow; use hir::{Complete, Function, HasContainer, ItemContainer, MethodCandidateCallback}; use ide_db::FxHashSet; +use itertools::Either; use syntax::SmolStr; use crate::{ @@ -146,11 +147,14 @@ pub(crate) fn complete_undotted_self( _ => return, }; - let ty = self_param.ty(ctx.db); + let (param_name, ty) = match self_param { + Either::Left(self_param) => ("self", &self_param.ty(ctx.db)), + Either::Right(this_param) => ("this", this_param.ty()), + }; complete_fields( acc, ctx, - &ty, + ty, |acc, field, ty| { acc.add_field( ctx, @@ -163,15 +167,17 @@ pub(crate) fn complete_undotted_self( in_breakable: expr_ctx.in_breakable, }, }, - Some(SmolStr::new_static("self")), + Some(SmolStr::new_static(param_name)), field, &ty, ) }, - |acc, field, ty| acc.add_tuple_field(ctx, Some(SmolStr::new_static("self")), field, &ty), + |acc, field, ty| { + acc.add_tuple_field(ctx, Some(SmolStr::new_static(param_name)), field, &ty) + }, false, ); - complete_methods(ctx, &ty, &ctx.traits_in_scope(), |func| { + complete_methods(ctx, ty, &ctx.traits_in_scope(), |func| { acc.add_method( ctx, &DotAccess { @@ -184,7 +190,7 @@ pub(crate) fn complete_undotted_self( }, }, func, - Some(SmolStr::new_static("self")), + Some(SmolStr::new_static(param_name)), None, ) }); @@ -1073,6 +1079,96 @@ impl Foo { fn foo(&mut self) { $0 } }"#, ); } + #[test] + fn completes_bare_fields_and_methods_in_this_closure() { + check_no_kw( + r#" +//- minicore: fn +struct Foo { field: i32 } + +impl Foo { fn foo(&mut self) { let _: fn(&mut Self) = |this| { $0 } } }"#, + expect![[r#" + fd this.field i32 + me this.foo() fn(&mut self) + lc self &mut Foo + lc this &mut Foo + md core + sp Self Foo + st Foo Foo + tt Fn + tt FnMut + tt FnOnce + bt u32 u32 + "#]], + ); + } + + #[test] + fn completes_bare_fields_and_methods_in_other_closure() { + check_no_kw( + r#" +//- minicore: fn +struct Foo { field: i32 } + +impl Foo { fn foo(&self) { let _: fn(&Self) = |foo| { $0 } } }"#, + expect![[r#" + fd self.field i32 + me self.foo() fn(&self) + lc foo &Foo + lc self &Foo + md core + sp Self Foo + st Foo Foo + tt Fn + tt FnMut + tt FnOnce + bt u32 u32 + "#]], + ); + + check_no_kw( + r#" +//- minicore: fn +struct Foo { field: i32 } + +impl Foo { fn foo(&self) { let _: fn(&Self) = || { $0 } } }"#, + expect![[r#" + fd self.field i32 + me self.foo() fn(&self) + lc self &Foo + md core + sp Self Foo + st Foo Foo + tt Fn + tt FnMut + tt FnOnce + bt u32 u32 + "#]], + ); + + check_no_kw( + r#" +//- minicore: fn +struct Foo { field: i32 } + +impl Foo { fn foo(&self) { let _: fn(&Self, &Self) = |foo, other| { $0 } } }"#, + expect![[r#" + fd self.field i32 + me self.foo() fn(&self) + lc foo &Foo + lc other &Foo + lc self &Foo + md core + sp Self Foo + st Foo Foo + tt Fn + tt FnMut + tt FnOnce + bt u32 u32 + "#]], + ); + } + #[test] fn macro_completion_after_dot() { check_no_kw( diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 23318e1d1991..7502026c6fc2 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -157,7 +157,7 @@ pub(crate) struct PathExprCtx<'db> { pub(crate) after_amp: bool, /// The surrounding RecordExpression we are completing a functional update pub(crate) is_func_update: Option, - pub(crate) self_param: Option, + pub(crate) self_param: Option>>, pub(crate) innermost_ret_ty: Option>, pub(crate) innermost_breakable_ty: Option>, pub(crate) impl_: Option, diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs index e761da7152c6..71c64a8366a5 100644 --- a/crates/ide-completion/src/context/analysis.rs +++ b/crates/ide-completion/src/context/analysis.rs @@ -1286,10 +1286,26 @@ fn classify_name_ref<'db>( ) } }; - let find_fn_self_param = |it| match it { - ast::Item::Fn(fn_) => Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))), - ast::Item::MacroCall(_) => None, - _ => Some(None), + let fn_self_param = + |fn_: ast::Fn| sema.to_def(&fn_).and_then(|it| it.self_param(sema.db)); + let closure_this_param = |closure: ast::ClosureExpr| { + if closure.param_list()?.params().next()?.pat()?.syntax().text() != "this" { + return None; + } + sema.type_of_expr(&closure.into()) + .and_then(|it| it.original.as_callable(sema.db)) + .and_then(|it| it.params().into_iter().next()) + }; + let find_fn_self_param = |it: SyntaxNode| { + match_ast! { + match it { + ast::Fn(fn_) => Some(fn_self_param(fn_).map(Either::Left)), + ast::ClosureExpr(f) => closure_this_param(f).map(Either::Right).map(Some), + ast::MacroCall(_) => None, + ast::Item(_) => Some(None), + _ => None, + } + } }; match find_node_in_file_compensated(sema, original_file, &expr) { @@ -1302,7 +1318,6 @@ fn classify_name_ref<'db>( let self_param = sema .ancestors_with_macros(it.syntax().clone()) - .filter_map(ast::Item::cast) .find_map(find_fn_self_param) .flatten(); (innermost_ret_ty, self_param) diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index c0f09e1d950d..aa9b05ecffec 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -3239,6 +3239,48 @@ impl S { ) } + #[test] + fn field_access_includes_closure_this_param() { + check_edit( + "length", + r#" +//- minicore: fn +struct S { + length: i32 +} + +impl S { + fn pack(&mut self, f: impl FnOnce(&mut Self, i32)) { + self.length += 1; + f(self, 3); + self.length -= 1; + } + + fn some_fn(&mut self) { + self.pack(|this, n| len$0); + } +} +"#, + r#" +struct S { + length: i32 +} + +impl S { + fn pack(&mut self, f: impl FnOnce(&mut Self, i32)) { + self.length += 1; + f(self, 3); + self.length -= 1; + } + + fn some_fn(&mut self) { + self.pack(|this, n| this.length); + } +} +"#, + ) + } + #[test] fn notable_traits_method_relevance() { check_kinds(