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:
parent
659aa56adb
commit
c00dfa3a11
7 changed files with 274 additions and 63 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue