Heuristic sensing parenthesis completion of fields

We have conducted heuristic sensing on method parentheses, but it cannot complete fields

Example
---
```rust
struct Foo { far: i32 }
impl Foo {
    fn foo(&self) {}
}
fn foo() -> (i32, i32) {
    let foo = Foo { far: 4 };
    foo.f$0
    (2, 3)
}
```

**Before this PR**:

```text
me foo()  fn(&self)
...
```

**After this PR**:

```text
fd far          i32
me foo()  fn(&self)
...
```
This commit is contained in:
A4-Tacks 2025-09-18 13:26:16 +08:00
parent 659aa56adb
commit c00dfa3a11
No known key found for this signature in database
GPG key ID: DBD861323040663B
7 changed files with 274 additions and 63 deletions

View file

@ -25,9 +25,7 @@ pub(crate) fn complete_dot(
_ => return,
};
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
let is_method_access_with_parens =
matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
let has_parens = matches!(dot_access.kind, DotAccessKind::Method);
let traits_in_scope = ctx.traits_in_scope();
// Suggest .await syntax for types that implement Future trait
@ -48,7 +46,7 @@ pub(crate) fn complete_dot(
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
}
it @ DotAccessKind::Method { .. } => *it,
it @ DotAccessKind::Method => *it,
};
let dot_access = DotAccess {
receiver: dot_access.receiver.clone(),
@ -67,8 +65,7 @@ pub(crate) fn complete_dot(
acc.add_field(ctx, &dot_access, Some(await_str.clone()), field, &ty)
},
|acc, field, ty| acc.add_tuple_field(ctx, Some(await_str.clone()), field, &ty),
is_field_access,
is_method_access_with_parens,
has_parens,
);
complete_methods(ctx, &future_output, &traits_in_scope, |func| {
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()), None)
@ -82,8 +79,7 @@ pub(crate) fn complete_dot(
receiver_ty,
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
|acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
is_field_access,
is_method_access_with_parens,
has_parens,
);
complete_methods(ctx, receiver_ty, &traits_in_scope, |func| {
acc.add_method(ctx, dot_access, func, None, None)
@ -112,7 +108,7 @@ pub(crate) fn complete_dot(
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
}
it @ DotAccessKind::Method { .. } => *it,
it @ DotAccessKind::Method => *it,
};
let dot_access = DotAccess {
receiver: dot_access.receiver.clone(),
@ -173,7 +169,6 @@ pub(crate) fn complete_undotted_self(
)
},
|acc, field, ty| acc.add_tuple_field(ctx, Some(SmolStr::new_static("self")), field, &ty),
true,
false,
);
complete_methods(ctx, &ty, &ctx.traits_in_scope(), |func| {
@ -182,7 +177,7 @@ pub(crate) fn complete_undotted_self(
&DotAccess {
receiver: None,
receiver_ty: None,
kind: DotAccessKind::Method { has_parens: false },
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
ctx: DotAccessExprCtx {
in_block_expr: expr_ctx.in_block_expr,
in_breakable: expr_ctx.in_breakable,
@ -201,15 +196,13 @@ fn complete_fields(
receiver: &hir::Type<'_>,
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type<'_>),
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type<'_>),
is_field_access: bool,
is_method_access_with_parens: bool,
has_parens: bool,
) {
let mut seen_names = FxHashSet::default();
for receiver in receiver.autoderef(ctx.db) {
for (field, ty) in receiver.fields(ctx.db) {
if seen_names.insert(field.name(ctx.db))
&& (is_field_access
|| (is_method_access_with_parens && (ty.is_fn() || ty.is_closure())))
&& (!has_parens || ty.is_fn() || ty.is_closure())
{
named_field(acc, field, ty);
}
@ -218,8 +211,7 @@ fn complete_fields(
// Tuples are always the last type in a deref chain, so just check if the name is
// already seen without inserting into the hashset.
if !seen_names.contains(&hir::Name::new_tuple_field(i))
&& (is_field_access
|| (is_method_access_with_parens && (ty.is_fn() || ty.is_closure())))
&& (!has_parens || ty.is_fn() || ty.is_closure())
{
// Tuple fields are always public (tuple struct fields are handled above).
tuple_index(acc, i, ty);
@ -1364,18 +1356,71 @@ fn foo() {
r#"
struct Foo { baz: fn() }
impl Foo {
fn bar<T>(self, t: T): T { t }
fn bar<T>(self, t: T) -> T { t }
}
fn baz() {
let foo = Foo{ baz: || {} };
foo.ba$0::<>;
foo.ba$0;
}
"#,
expect![[r#"
me bar() fn(self, T)
fd baz fn()
me bar() fn(self, T) -> T
"#]],
);
check_edit(
"baz",
r#"
struct Foo { baz: fn() }
impl Foo {
fn bar<T>(self, t: T) -> T { t }
}
fn baz() {
let foo = Foo{ baz: || {} };
foo.ba$0;
}
"#,
r#"
struct Foo { baz: fn() }
impl Foo {
fn bar<T>(self, t: T) -> T { t }
}
fn baz() {
let foo = Foo{ baz: || {} };
(foo.baz)();
}
"#,
);
check_edit(
"bar",
r#"
struct Foo { baz: fn() }
impl Foo {
fn bar<T>(self, t: T) -> T { t }
}
fn baz() {
let foo = Foo{ baz: || {} };
foo.ba$0;
}
"#,
r#"
struct Foo { baz: fn() }
impl Foo {
fn bar<T>(self, t: T) -> T { t }
}
fn baz() {
let foo = Foo{ baz: || {} };
foo.bar(${1:t})$0;
}
"#,
);
}
#[test]

View file

@ -43,7 +43,7 @@ pub(crate) fn complete_postfix(
DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
receiver_is_ambiguous_float_literal
}
DotAccessKind::Method { .. } => false,
DotAccessKind::Method => false,
},
),
_ => return,

View file

@ -405,9 +405,7 @@ pub(crate) enum DotAccessKind {
/// like `0.$0`
receiver_is_ambiguous_float_literal: bool,
},
Method {
has_parens: bool,
},
Method,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

View file

@ -891,44 +891,53 @@ fn classify_name_ref<'db>(
return Some(make_res(kind));
}
let field_expr_handle = |recviver, node| {
let receiver = find_opt_node_in_file(original_file, recviver);
let receiver_is_ambiguous_float_literal = match &receiver {
Some(ast::Expr::Literal(l)) => matches! {
l.kind(),
ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
},
_ => false,
};
let receiver_is_part_of_indivisible_expression = match &receiver {
Some(ast::Expr::IfExpr(_)) => {
let next_token_kind =
next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
next_token_kind == Some(SyntaxKind::ELSE_KW)
}
_ => false,
};
if receiver_is_part_of_indivisible_expression {
return None;
}
let mut receiver_ty = receiver.as_ref().and_then(|it| sema.type_of_expr(it));
if receiver_is_ambiguous_float_literal {
// `123.|` is parsed as a float but should actually be an integer.
always!(receiver_ty.as_ref().is_none_or(|receiver_ty| receiver_ty.original.is_float()));
receiver_ty =
Some(TypeInfo { original: hir::BuiltinType::i32().ty(sema.db), adjusted: None });
}
let kind = NameRefKind::DotAccess(DotAccess {
receiver_ty,
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
receiver,
ctx: DotAccessExprCtx {
in_block_expr: is_in_block(node),
in_breakable: is_in_breakable(node).unzip().0,
},
});
Some(make_res(kind))
};
let segment = match_ast! {
match parent {
ast::PathSegment(segment) => segment,
ast::FieldExpr(field) => {
let receiver = find_opt_node_in_file(original_file, field.expr());
let receiver_is_ambiguous_float_literal = match &receiver {
Some(ast::Expr::Literal(l)) => matches! {
l.kind(),
ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
},
_ => false,
};
let receiver_is_part_of_indivisible_expression = match &receiver {
Some(ast::Expr::IfExpr(_)) => {
let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
next_token_kind == Some(SyntaxKind::ELSE_KW)
},
_ => false
};
if receiver_is_part_of_indivisible_expression {
return None;
}
let mut receiver_ty = receiver.as_ref().and_then(|it| sema.type_of_expr(it));
if receiver_is_ambiguous_float_literal {
// `123.|` is parsed as a float but should actually be an integer.
always!(receiver_ty.as_ref().is_none_or(|receiver_ty| receiver_ty.original.is_float()));
receiver_ty = Some(TypeInfo { original: hir::BuiltinType::i32().ty(sema.db), adjusted: None });
}
let kind = NameRefKind::DotAccess(DotAccess {
receiver_ty,
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
receiver,
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()).unzip().0 }
});
return Some(make_res(kind));
return field_expr_handle(field.expr(), field.syntax());
},
ast::ExternCrate(_) => {
let kind = NameRefKind::ExternCrate;
@ -937,9 +946,12 @@ fn classify_name_ref<'db>(
ast::MethodCallExpr(method) => {
let receiver = find_opt_node_in_file(original_file, method.receiver());
let has_parens = has_parens(&method);
if !has_parens && let Some(res) = field_expr_handle(method.receiver(), method.syntax()) {
return Some(res)
}
let kind = NameRefKind::DotAccess(DotAccess {
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
kind: DotAccessKind::Method { has_parens },
kind: DotAccessKind::Method,
receiver,
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()).unzip().0 }
});

View file

@ -170,8 +170,7 @@ pub(crate) fn render_field(
builder.insert(receiver.syntax().text_range().start(), "(".to_owned());
builder.insert(ctx.source_range().end(), ")".to_owned());
let is_parens_needed =
!matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
let is_parens_needed = !matches!(dot_access.kind, DotAccessKind::Method);
if is_parens_needed {
builder.insert(ctx.source_range().end(), "()".to_owned());

View file

@ -93,8 +93,8 @@ fn render(
has_call_parens,
..
}) => (false, has_call_parens, ctx.completion.config.snippet_cap),
FuncKind::Method(&DotAccess { kind: DotAccessKind::Method { has_parens }, .. }, _) => {
(true, has_parens, ctx.completion.config.snippet_cap)
FuncKind::Method(&DotAccess { kind: DotAccessKind::Method, .. }, _) => {
(true, true, ctx.completion.config.snippet_cap)
}
FuncKind::Method(DotAccess { kind: DotAccessKind::Field { .. }, .. }, _) => {
(true, false, ctx.completion.config.snippet_cap)

View file

@ -2936,6 +2936,43 @@ fn foo() {
);
}
#[test]
fn ambiguous_float_literal_in_ambiguous_method_call() {
check(
r#"
#![rustc_coherence_is_core]
impl i32 {
pub fn int_method(self) {}
}
impl f64 {
pub fn float_method(self) {}
}
fn foo() -> (i32, i32) {
1.$0
(2, 3)
}
"#,
expect![[r#"
me int_method() fn(self)
sn box Box::new(expr)
sn call function(expr)
sn const const {}
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
sn let let
sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn return return expr
sn unsafe unsafe {}
"#]],
);
}
#[test]
fn let_in_condition() {
check_edit("let", r#"fn f() { if $0 {} }"#, r#"fn f() { if let $1 = $0 {} }"#);
@ -3113,6 +3150,126 @@ fn let_in_previous_line_of_ambiguous_expr() {
);
}
#[test]
fn field_in_previous_line_of_ambiguous_expr() {
check(
r#"
struct Foo { field: i32 }
impl Foo {
fn method(&self) {}
}
fn foo() -> (i32, i32) {
let foo = Foo { field: 4 };
foo.$0
(2, 3)
}"#,
expect![[r#"
fd field i32
me method() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn const const {}
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
sn let let
sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn return return expr
sn unsafe unsafe {}
"#]],
);
check(
r#"
struct Foo { field: i32 }
impl Foo {
fn method(&self) {}
}
fn foo() -> (i32, i32) {
let foo = Foo { field: 4 };
foo.a$0
(2, 3)
}"#,
expect![[r#"
fd field i32
me method() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn const const {}
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
sn let let
sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn return return expr
sn unsafe unsafe {}
"#]],
);
}
#[test]
fn fn_field_in_previous_line_of_ambiguous_expr() {
check(
r#"
struct Foo { field: fn() }
impl Foo {
fn method(&self) {}
}
fn foo() -> (i32, i32) {
let foo = Foo { field: || () };
foo.$0
(2, 3)
}"#,
expect![[r#"
fd field fn()
me method() fn(&self)
sn box Box::new(expr)
sn call function(expr)
sn const const {}
sn dbg dbg!(expr)
sn dbgr dbg!(&expr)
sn deref *expr
sn let let
sn letm let mut
sn match match expr {}
sn ref &expr
sn refm &mut expr
sn return return expr
sn unsafe unsafe {}
"#]],
);
check_edit(
"field",
r#"
struct Foo { field: fn() }
impl Foo {
fn method(&self) {}
}
fn foo() -> (i32, i32) {
let foo = Foo { field: || () };
foo.a$0
(2, 3)
}"#,
r#"
struct Foo { field: fn() }
impl Foo {
fn method(&self) {}
}
fn foo() -> (i32, i32) {
let foo = Foo { field: || () };
(foo.field)()
(2, 3)
}"#,
);
}
#[test]
fn private_inherent_and_public_trait() {
check(