Merge pull request #21166 from A4-Tacks/fly-closure-this-param

Support undotted-self for `this` param closure
This commit is contained in:
Chayim Refael Friedman 2025-12-21 05:37:24 +00:00 committed by GitHub
commit b5fbcc6917
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 165 additions and 12 deletions

View File

@ -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(

View File

@ -156,7 +156,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<ast::RecordExpr>,
pub(crate) self_param: Option<hir::SelfParam>,
pub(crate) self_param: Option<Either<hir::SelfParam, hir::Param<'db>>>,
pub(crate) innermost_ret_ty: Option<hir::Type<'db>>,
pub(crate) innermost_breakable_ty: Option<hir::Type<'db>>,
pub(crate) impl_: Option<ast::Impl>,

View File

@ -1319,10 +1319,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) {
@ -1335,7 +1351,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)

View File

@ -3279,6 +3279,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(