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)
...
```
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 72b245c..511b593 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -25,9 +25,7 @@
_ => 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 @@
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 @@
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 @@
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 @@
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 @@
)
},
|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 @@
&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 @@
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 @@
// 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 @@
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]
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index 4474d61..7076153 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -43,7 +43,7 @@
DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
receiver_is_ambiguous_float_literal
}
- DotAccessKind::Method { .. } => false,
+ DotAccessKind::Method => false,
},
),
_ => return,
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index c95b83e..b245c0d 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -405,9 +405,7 @@
/// like `0.$0`
receiver_is_ambiguous_float_literal: bool,
},
- Method {
- has_parens: bool,
- },
+ Method,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index f0a03de..c01b544 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -891,44 +891,53 @@
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 @@
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 }
});
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 77a2a3a..bc5589a 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -170,8 +170,7 @@
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());
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index c466019..3235323 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -93,8 +93,8 @@
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)
diff --git a/crates/ide-completion/src/tests/expression.rs b/crates/ide-completion/src/tests/expression.rs
index f75fa79..09af635 100644
--- a/crates/ide-completion/src/tests/expression.rs
+++ b/crates/ide-completion/src/tests/expression.rs
@@ -2937,6 +2937,43 @@
}
#[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 {} }"#);
}
@@ -3114,6 +3151,126 @@
}
#[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(
r#"