Skip to content

Commit 6e397d3

Browse files
Merge pull request rust-lang#20889 from A4-Tacks/heuristic-field-expr
Heuristic sensing parenthesis completion of fields
2 parents 88e4ea5 + 03b8682 commit 6e397d3

File tree

7 files changed

+274
-63
lines changed

7 files changed

+274
-63
lines changed

src/tools/rust-analyzer/crates/ide-completion/src/completions/dot.rs

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ pub(crate) fn complete_dot(
2525
_ => return,
2626
};
2727

28-
let is_field_access = matches!(dot_access.kind, DotAccessKind::Field { .. });
29-
let is_method_access_with_parens =
30-
matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
28+
let has_parens = matches!(dot_access.kind, DotAccessKind::Method);
3129
let traits_in_scope = ctx.traits_in_scope();
3230

3331
// Suggest .await syntax for types that implement Future trait
@@ -48,7 +46,7 @@ pub(crate) fn complete_dot(
4846
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
4947
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
5048
}
51-
it @ DotAccessKind::Method { .. } => *it,
49+
it @ DotAccessKind::Method => *it,
5250
};
5351
let dot_access = DotAccess {
5452
receiver: dot_access.receiver.clone(),
@@ -67,8 +65,7 @@ pub(crate) fn complete_dot(
6765
acc.add_field(ctx, &dot_access, Some(await_str.clone()), field, &ty)
6866
},
6967
|acc, field, ty| acc.add_tuple_field(ctx, Some(await_str.clone()), field, &ty),
70-
is_field_access,
71-
is_method_access_with_parens,
68+
has_parens,
7269
);
7370
complete_methods(ctx, &future_output, &traits_in_scope, |func| {
7471
acc.add_method(ctx, &dot_access, func, Some(await_str.clone()), None)
@@ -82,8 +79,7 @@ pub(crate) fn complete_dot(
8279
receiver_ty,
8380
|acc, field, ty| acc.add_field(ctx, dot_access, None, field, &ty),
8481
|acc, field, ty| acc.add_tuple_field(ctx, None, field, &ty),
85-
is_field_access,
86-
is_method_access_with_parens,
82+
has_parens,
8783
);
8884
complete_methods(ctx, receiver_ty, &traits_in_scope, |func| {
8985
acc.add_method(ctx, dot_access, func, None, None)
@@ -112,7 +108,7 @@ pub(crate) fn complete_dot(
112108
DotAccessKind::Field { receiver_is_ambiguous_float_literal: _ } => {
113109
DotAccessKind::Field { receiver_is_ambiguous_float_literal: false }
114110
}
115-
it @ DotAccessKind::Method { .. } => *it,
111+
it @ DotAccessKind::Method => *it,
116112
};
117113
let dot_access = DotAccess {
118114
receiver: dot_access.receiver.clone(),
@@ -173,7 +169,6 @@ pub(crate) fn complete_undotted_self(
173169
)
174170
},
175171
|acc, field, ty| acc.add_tuple_field(ctx, Some(SmolStr::new_static("self")), field, &ty),
176-
true,
177172
false,
178173
);
179174
complete_methods(ctx, &ty, &ctx.traits_in_scope(), |func| {
@@ -182,7 +177,7 @@ pub(crate) fn complete_undotted_self(
182177
&DotAccess {
183178
receiver: None,
184179
receiver_ty: None,
185-
kind: DotAccessKind::Method { has_parens: false },
180+
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal: false },
186181
ctx: DotAccessExprCtx {
187182
in_block_expr: expr_ctx.in_block_expr,
188183
in_breakable: expr_ctx.in_breakable,
@@ -201,15 +196,13 @@ fn complete_fields(
201196
receiver: &hir::Type<'_>,
202197
mut named_field: impl FnMut(&mut Completions, hir::Field, hir::Type<'_>),
203198
mut tuple_index: impl FnMut(&mut Completions, usize, hir::Type<'_>),
204-
is_field_access: bool,
205-
is_method_access_with_parens: bool,
199+
has_parens: bool,
206200
) {
207201
let mut seen_names = FxHashSet::default();
208202
for receiver in receiver.autoderef(ctx.db) {
209203
for (field, ty) in receiver.fields(ctx.db) {
210204
if seen_names.insert(field.name(ctx.db))
211-
&& (is_field_access
212-
|| (is_method_access_with_parens && (ty.is_fn() || ty.is_closure())))
205+
&& (!has_parens || ty.is_fn() || ty.is_closure())
213206
{
214207
named_field(acc, field, ty);
215208
}
@@ -218,8 +211,7 @@ fn complete_fields(
218211
// Tuples are always the last type in a deref chain, so just check if the name is
219212
// already seen without inserting into the hashset.
220213
if !seen_names.contains(&hir::Name::new_tuple_field(i))
221-
&& (is_field_access
222-
|| (is_method_access_with_parens && (ty.is_fn() || ty.is_closure())))
214+
&& (!has_parens || ty.is_fn() || ty.is_closure())
223215
{
224216
// Tuple fields are always public (tuple struct fields are handled above).
225217
tuple_index(acc, i, ty);
@@ -1364,18 +1356,71 @@ fn foo() {
13641356
r#"
13651357
struct Foo { baz: fn() }
13661358
impl Foo {
1367-
fn bar<T>(self, t: T): T { t }
1359+
fn bar<T>(self, t: T) -> T { t }
13681360
}
13691361
13701362
fn baz() {
13711363
let foo = Foo{ baz: || {} };
1372-
foo.ba$0::<>;
1364+
foo.ba$0;
13731365
}
13741366
"#,
13751367
expect![[r#"
1376-
me bar(…) fn(self, T)
1368+
fd baz fn()
1369+
me bar(…) fn(self, T) -> T
13771370
"#]],
13781371
);
1372+
1373+
check_edit(
1374+
"baz",
1375+
r#"
1376+
struct Foo { baz: fn() }
1377+
impl Foo {
1378+
fn bar<T>(self, t: T) -> T { t }
1379+
}
1380+
1381+
fn baz() {
1382+
let foo = Foo{ baz: || {} };
1383+
foo.ba$0;
1384+
}
1385+
"#,
1386+
r#"
1387+
struct Foo { baz: fn() }
1388+
impl Foo {
1389+
fn bar<T>(self, t: T) -> T { t }
1390+
}
1391+
1392+
fn baz() {
1393+
let foo = Foo{ baz: || {} };
1394+
(foo.baz)();
1395+
}
1396+
"#,
1397+
);
1398+
1399+
check_edit(
1400+
"bar",
1401+
r#"
1402+
struct Foo { baz: fn() }
1403+
impl Foo {
1404+
fn bar<T>(self, t: T) -> T { t }
1405+
}
1406+
1407+
fn baz() {
1408+
let foo = Foo{ baz: || {} };
1409+
foo.ba$0;
1410+
}
1411+
"#,
1412+
r#"
1413+
struct Foo { baz: fn() }
1414+
impl Foo {
1415+
fn bar<T>(self, t: T) -> T { t }
1416+
}
1417+
1418+
fn baz() {
1419+
let foo = Foo{ baz: || {} };
1420+
foo.bar(${1:t})$0;
1421+
}
1422+
"#,
1423+
);
13791424
}
13801425

13811426
#[test]

src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(crate) fn complete_postfix(
4343
DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
4444
receiver_is_ambiguous_float_literal
4545
}
46-
DotAccessKind::Method { .. } => false,
46+
DotAccessKind::Method => false,
4747
},
4848
),
4949
_ => return,

src/tools/rust-analyzer/crates/ide-completion/src/context.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,7 @@ pub(crate) enum DotAccessKind {
405405
/// like `0.$0`
406406
receiver_is_ambiguous_float_literal: bool,
407407
},
408-
Method {
409-
has_parens: bool,
410-
},
408+
Method,
411409
}
412410

413411
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

src/tools/rust-analyzer/crates/ide-completion/src/context/analysis.rs

Lines changed: 47 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -891,44 +891,53 @@ fn classify_name_ref<'db>(
891891
return Some(make_res(kind));
892892
}
893893

894+
let field_expr_handle = |receiver, node| {
895+
let receiver = find_opt_node_in_file(original_file, receiver);
896+
let receiver_is_ambiguous_float_literal = match &receiver {
897+
Some(ast::Expr::Literal(l)) => matches! {
898+
l.kind(),
899+
ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
900+
},
901+
_ => false,
902+
};
903+
904+
let receiver_is_part_of_indivisible_expression = match &receiver {
905+
Some(ast::Expr::IfExpr(_)) => {
906+
let next_token_kind =
907+
next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
908+
next_token_kind == Some(SyntaxKind::ELSE_KW)
909+
}
910+
_ => false,
911+
};
912+
if receiver_is_part_of_indivisible_expression {
913+
return None;
914+
}
915+
916+
let mut receiver_ty = receiver.as_ref().and_then(|it| sema.type_of_expr(it));
917+
if receiver_is_ambiguous_float_literal {
918+
// `123.|` is parsed as a float but should actually be an integer.
919+
always!(receiver_ty.as_ref().is_none_or(|receiver_ty| receiver_ty.original.is_float()));
920+
receiver_ty =
921+
Some(TypeInfo { original: hir::BuiltinType::i32().ty(sema.db), adjusted: None });
922+
}
923+
924+
let kind = NameRefKind::DotAccess(DotAccess {
925+
receiver_ty,
926+
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
927+
receiver,
928+
ctx: DotAccessExprCtx {
929+
in_block_expr: is_in_block(node),
930+
in_breakable: is_in_breakable(node).unzip().0,
931+
},
932+
});
933+
Some(make_res(kind))
934+
};
935+
894936
let segment = match_ast! {
895937
match parent {
896938
ast::PathSegment(segment) => segment,
897939
ast::FieldExpr(field) => {
898-
let receiver = find_opt_node_in_file(original_file, field.expr());
899-
let receiver_is_ambiguous_float_literal = match &receiver {
900-
Some(ast::Expr::Literal(l)) => matches! {
901-
l.kind(),
902-
ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
903-
},
904-
_ => false,
905-
};
906-
907-
let receiver_is_part_of_indivisible_expression = match &receiver {
908-
Some(ast::Expr::IfExpr(_)) => {
909-
let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
910-
next_token_kind == Some(SyntaxKind::ELSE_KW)
911-
},
912-
_ => false
913-
};
914-
if receiver_is_part_of_indivisible_expression {
915-
return None;
916-
}
917-
918-
let mut receiver_ty = receiver.as_ref().and_then(|it| sema.type_of_expr(it));
919-
if receiver_is_ambiguous_float_literal {
920-
// `123.|` is parsed as a float but should actually be an integer.
921-
always!(receiver_ty.as_ref().is_none_or(|receiver_ty| receiver_ty.original.is_float()));
922-
receiver_ty = Some(TypeInfo { original: hir::BuiltinType::i32().ty(sema.db), adjusted: None });
923-
}
924-
925-
let kind = NameRefKind::DotAccess(DotAccess {
926-
receiver_ty,
927-
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
928-
receiver,
929-
ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()).unzip().0 }
930-
});
931-
return Some(make_res(kind));
940+
return field_expr_handle(field.expr(), field.syntax());
932941
},
933942
ast::ExternCrate(_) => {
934943
let kind = NameRefKind::ExternCrate;
@@ -937,9 +946,12 @@ fn classify_name_ref<'db>(
937946
ast::MethodCallExpr(method) => {
938947
let receiver = find_opt_node_in_file(original_file, method.receiver());
939948
let has_parens = has_parens(&method);
949+
if !has_parens && let Some(res) = field_expr_handle(method.receiver(), method.syntax()) {
950+
return Some(res)
951+
}
940952
let kind = NameRefKind::DotAccess(DotAccess {
941953
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
942-
kind: DotAccessKind::Method { has_parens },
954+
kind: DotAccessKind::Method,
943955
receiver,
944956
ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()).unzip().0 }
945957
});

src/tools/rust-analyzer/crates/ide-completion/src/render.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,7 @@ pub(crate) fn render_field(
171171
builder.insert(receiver.syntax().text_range().start(), "(".to_owned());
172172
builder.insert(ctx.source_range().end(), ")".to_owned());
173173

174-
let is_parens_needed =
175-
!matches!(dot_access.kind, DotAccessKind::Method { has_parens: true });
174+
let is_parens_needed = !matches!(dot_access.kind, DotAccessKind::Method);
176175

177176
if is_parens_needed {
178177
builder.insert(ctx.source_range().end(), "()".to_owned());

src/tools/rust-analyzer/crates/ide-completion/src/render/function.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ fn render(
9393
has_call_parens,
9494
..
9595
}) => (false, has_call_parens, ctx.completion.config.snippet_cap),
96-
FuncKind::Method(&DotAccess { kind: DotAccessKind::Method { has_parens }, .. }, _) => {
97-
(true, has_parens, ctx.completion.config.snippet_cap)
96+
FuncKind::Method(&DotAccess { kind: DotAccessKind::Method, .. }, _) => {
97+
(true, true, ctx.completion.config.snippet_cap)
9898
}
9999
FuncKind::Method(DotAccess { kind: DotAccessKind::Field { .. }, .. }, _) => {
100100
(true, false, ctx.completion.config.snippet_cap)

0 commit comments

Comments
 (0)