Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-completion/src/completions/dot.rs2
-rw-r--r--crates/ide-completion/src/config.rs1
-rw-r--r--crates/ide-completion/src/render.rs18
-rw-r--r--crates/ide-completion/src/render/function.rs184
-rw-r--r--crates/ide-completion/src/tests.rs1
-rw-r--r--crates/ide-completion/src/tests/flyimport.rs16
-rw-r--r--crates/ide-completion/src/tests/raw_identifiers.rs8
-rw-r--r--crates/ide-completion/src/tests/special.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs5
-rw-r--r--crates/rust-analyzer/src/integrated_benchmarks.rs3
-rw-r--r--docs/user/generated_config.adoc7
-rw-r--r--editors/code/package.json10
12 files changed, 217 insertions, 40 deletions
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index d55bc3ea5d..f2c360a9d5 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -600,7 +600,7 @@ fn foo(a: A) { a.$0 }
struct A {}
trait Trait { fn the_method(&self); }
impl Trait for A {}
-fn foo(a: A) { a.the_method()$0 }
+fn foo(a: A) { a.the_method();$0 }
"#,
);
}
diff --git a/crates/ide-completion/src/config.rs b/crates/ide-completion/src/config.rs
index d885b82ec9..0d403f49b7 100644
--- a/crates/ide-completion/src/config.rs
+++ b/crates/ide-completion/src/config.rs
@@ -19,6 +19,7 @@ pub struct CompletionConfig {
pub term_search_fuel: u64,
pub full_function_signatures: bool,
pub callable: Option<CallableSnippets>,
+ pub add_semicolon_to_unit: bool,
pub snippet_cap: Option<SnippetCap>,
pub insert_use: InsertUseConfig,
pub prefer_no_std: bool,
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index 2bec2eb87b..f2e9de9382 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -1185,7 +1185,7 @@ fn main() { fo$0 }
label: "main()",
source_range: 68..70,
delete: 68..70,
- insert: "main()$0",
+ insert: "main();$0",
kind: SymbolKind(
Function,
),
@@ -1244,7 +1244,7 @@ fn main() { let _: m::Spam = S$0 }
label: "main()",
source_range: 75..76,
delete: 75..76,
- insert: "main()$0",
+ insert: "main();$0",
kind: SymbolKind(
Function,
),
@@ -1331,7 +1331,7 @@ fn main() { som$0 }
label: "main()",
source_range: 56..59,
delete: 56..59,
- insert: "main()$0",
+ insert: "main();$0",
kind: SymbolKind(
Function,
),
@@ -1342,7 +1342,7 @@ fn main() { som$0 }
label: "something_deprecated()",
source_range: 56..59,
delete: 56..59,
- insert: "something_deprecated()$0",
+ insert: "something_deprecated();$0",
kind: SymbolKind(
Function,
),
@@ -1413,7 +1413,7 @@ impl S {
label: "bar()",
source_range: 94..94,
delete: 94..94,
- insert: "bar()$0",
+ insert: "bar();$0",
kind: SymbolKind(
Method,
),
@@ -1540,7 +1540,7 @@ fn foo(s: S) { s.$0 }
label: "the_method()",
source_range: 81..81,
delete: 81..81,
- insert: "the_method()$0",
+ insert: "the_method();$0",
kind: SymbolKind(
Method,
),
@@ -2789,7 +2789,7 @@ fn main() {
r#"
mod m { pub fn r#type {} }
fn main() {
- m::r#type()$0
+ m::r#type();$0
}
"#,
)
@@ -2963,7 +2963,7 @@ fn main() {
label: "flush()",
source_range: 193..193,
delete: 193..193,
- insert: "flush()$0",
+ insert: "flush();$0",
kind: SymbolKind(
Method,
),
@@ -2990,7 +2990,7 @@ fn main() {
label: "write()",
source_range: 193..193,
delete: 193..193,
- insert: "write()$0",
+ insert: "write();$0",
kind: SymbolKind(
Method,
),
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 29820cb203..7e5e69665f 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -1,10 +1,12 @@
//! Renderer for function calls.
+use std::ops::ControlFlow;
+
use hir::{db::HirDatabase, AsAssocItem, HirDisplay};
use ide_db::{SnippetCap, SymbolKind};
use itertools::Itertools;
use stdx::{format_to, to_lower_snake_case};
-use syntax::{format_smolstr, AstNode, Edition, SmolStr, ToSmolStr};
+use syntax::{ast, format_smolstr, AstNode, Edition, SmolStr, SyntaxKind, ToSmolStr, T};
use crate::{
context::{CompletionContext, DotAccess, DotAccessKind, PathCompletionCtx, PathKind},
@@ -160,7 +162,16 @@ fn render(
.lookup_by(name.unescaped().display(db).to_smolstr());
if let Some((cap, (self_param, params))) = complete_call_parens {
- add_call_parens(&mut item, completion, cap, call, escaped_call, self_param, params);
+ add_call_parens(
+ &mut item,
+ completion,
+ cap,
+ call,
+ escaped_call,
+ self_param,
+ params,
+ &ret_type,
+ );
}
match ctx.import_to_add {
@@ -217,10 +228,11 @@ pub(super) fn add_call_parens<'b>(
escaped_name: SmolStr,
self_param: Option<hir::SelfParam>,
params: Vec<hir::Param>,
+ ret_type: &hir::Type,
) -> &'b mut Builder {
cov_mark::hit!(inserts_parens_for_function_calls);
- let (snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
+ let (mut snippet, label_suffix) = if self_param.is_none() && params.is_empty() {
(format!("{escaped_name}()$0"), "()")
} else {
builder.trigger_call_info();
@@ -265,6 +277,35 @@ pub(super) fn add_call_parens<'b>(
(snippet, "(…)")
};
+ if ret_type.is_unit() && ctx.config.add_semicolon_to_unit {
+ let next_non_trivia_token =
+ std::iter::successors(ctx.token.next_token(), |it| it.next_token())
+ .find(|it| !it.kind().is_trivia());
+ let in_match_arm = ctx.token.parent_ancestors().try_for_each(|ancestor| {
+ if ast::MatchArm::can_cast(ancestor.kind()) {
+ ControlFlow::Break(true)
+ } else if matches!(ancestor.kind(), SyntaxKind::EXPR_STMT | SyntaxKind::BLOCK_EXPR) {
+ ControlFlow::Break(false)
+ } else {
+ ControlFlow::Continue(())
+ }
+ });
+ // FIXME: This will assume expr macros are not inside match, we need to somehow go to the "parent" of the root node.
+ let in_match_arm = match in_match_arm {
+ ControlFlow::Continue(()) => false,
+ ControlFlow::Break(it) => it,
+ };
+ let complete_token = if in_match_arm { T![,] } else { T![;] };
+ if next_non_trivia_token.map(|it| it.kind()) != Some(complete_token) {
+ cov_mark::hit!(complete_semicolon);
+ let ch = if in_match_arm { ',' } else { ';' };
+ if snippet.ends_with("$0") {
+ snippet.insert(snippet.len() - "$0".len(), ch);
+ } else {
+ snippet.push(ch);
+ }
+ }
+ }
builder.label(SmolStr::from_iter([&name, label_suffix])).insert_snippet(cap, snippet)
}
@@ -393,7 +434,7 @@ fn main() { no_$0 }
"#,
r#"
fn no_args() {}
-fn main() { no_args()$0 }
+fn main() { no_args();$0 }
"#,
);
@@ -405,7 +446,7 @@ fn main() { with_$0 }
"#,
r#"
fn with_args(x: i32, y: String) {}
-fn main() { with_args(${1:x}, ${2:y})$0 }
+fn main() { with_args(${1:x}, ${2:y});$0 }
"#,
);
@@ -414,14 +455,14 @@ fn main() { with_args(${1:x}, ${2:y})$0 }
r#"
struct S;
impl S {
- fn foo(&self) {}
+ fn foo(&self) -> i32 { 0 }
}
fn bar(s: &S) { s.f$0 }
"#,
r#"
struct S;
impl S {
- fn foo(&self) {}
+ fn foo(&self) -> i32 { 0 }
}
fn bar(s: &S) { s.foo()$0 }
"#,
@@ -444,7 +485,7 @@ impl S {
fn foo(&self, x: i32) {}
}
fn bar(s: &S) {
- s.foo(${1:x})$0
+ s.foo(${1:x});$0
}
"#,
);
@@ -463,7 +504,7 @@ impl S {
struct S {}
impl S {
fn foo(&self, x: i32) {
- self.foo(${1:x})$0
+ self.foo(${1:x});$0
}
}
"#,
@@ -486,7 +527,7 @@ struct S;
impl S {
fn foo(&self) {}
}
-fn main() { S::foo(${1:&self})$0 }
+fn main() { S::foo(${1:&self});$0 }
"#,
);
}
@@ -503,7 +544,7 @@ fn main() { with_$0 }
"#,
r#"
fn with_args(x: i32, y: String) {}
-fn main() { with_args($0) }
+fn main() { with_args($0); }
"#,
);
}
@@ -518,7 +559,7 @@ fn main() { f$0 }
"#,
r#"
fn foo(_foo: i32, ___bar: bool, ho_ge_: String) {}
-fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_})$0 }
+fn main() { foo(${1:foo}, ${2:bar}, ${3:ho_ge_});$0 }
"#,
);
}
@@ -540,7 +581,7 @@ struct Foo {}
fn ref_arg(x: &Foo) {}
fn main() {
let x = Foo {};
- ref_arg(${1:&x})$0
+ ref_arg(${1:&x});$0
}
"#,
);
@@ -563,7 +604,7 @@ struct Foo {}
fn ref_arg(x: &mut Foo) {}
fn main() {
let x = Foo {};
- ref_arg(${1:&mut x})$0
+ ref_arg(${1:&mut x});$0
}
"#,
);
@@ -596,7 +637,7 @@ impl Bar {
fn main() {
let x = Foo {};
let y = Bar {};
- y.apply_foo(${1:&x})$0
+ y.apply_foo(${1:&x});$0
}
"#,
);
@@ -617,7 +658,7 @@ fn main() {
fn take_mutably(mut x: &i32) {}
fn main() {
- take_mutably(${1:x})$0
+ take_mutably(${1:x});$0
}
"#,
);
@@ -650,7 +691,7 @@ fn qux(Foo { bar }: Foo) {
}
fn main() {
- qux(${1:foo})$0
+ qux(${1:foo});$0
}
"#,
);
@@ -739,4 +780,113 @@ fn g(foo: (), #[baz = "qux"] mut bar: u32)
"#,
);
}
+
+ #[test]
+ fn complete_semicolon_for_unit() {
+ cov_mark::check!(complete_semicolon);
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo() {}
+fn bar() {
+ foo$0
+}
+"#,
+ r#"
+fn foo() {}
+fn bar() {
+ foo();$0
+}
+"#,
+ );
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo$0
+}
+"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo(${1:a});$0
+}
+"#,
+ );
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo$0;
+}
+"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo(${1:a})$0;
+}
+"#,
+ );
+ check_edit_with_config(
+ CompletionConfig { add_semicolon_to_unit: false, ..TEST_CONFIG },
+ r#"foo"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo$0
+}
+"#,
+ r#"
+fn foo(a: i32) {}
+fn bar() {
+ foo(${1:a})$0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn complete_comma_for_unit_match_arm() {
+ cov_mark::check!(complete_semicolon);
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo() {}
+fn bar() {
+ match Some(false) {
+ v => fo$0
+ }
+}
+"#,
+ r#"
+fn foo() {}
+fn bar() {
+ match Some(false) {
+ v => foo(),$0
+ }
+}
+"#,
+ );
+ check_edit(
+ r#"foo"#,
+ r#"
+fn foo() {}
+fn bar() {
+ match Some(false) {
+ v => fo$0,
+ }
+}
+"#,
+ r#"
+fn foo() {}
+fn bar() {
+ match Some(false) {
+ v => foo()$0,
+ }
+}
+"#,
+ );
+ }
}
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 04ba7e1f41..9d77d97007 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -70,6 +70,7 @@ pub(crate) const TEST_CONFIG: CompletionConfig = CompletionConfig {
term_search_fuel: 200,
full_function_signatures: false,
callable: Some(CallableSnippets::FillArguments),
+ add_semicolon_to_unit: true,
snippet_cap: SnippetCap::new(true),
insert_use: InsertUseConfig {
granularity: ImportGranularity::Crate,
diff --git a/crates/ide-completion/src/tests/flyimport.rs b/crates/ide-completion/src/tests/flyimport.rs
index 8350fdcc4c..0b532064fb 100644
--- a/crates/ide-completion/src/tests/flyimport.rs
+++ b/crates/ide-completion/src/tests/flyimport.rs
@@ -53,7 +53,7 @@ fn main() {
use dep::io::stdin;
fn main() {
- stdin()$0
+ stdin();$0
}
"#,
);
@@ -274,7 +274,7 @@ fn trait_function_fuzzy_completion() {
use dep::test_mod::TestTrait;
fn main() {
- dep::test_mod::TestStruct::weird_function()$0
+ dep::test_mod::TestStruct::weird_function();$0
}
"#,
);
@@ -368,7 +368,7 @@ use dep::test_mod::TestTrait;
fn main() {
let test_struct = dep::test_mod::TestStruct {};
- test_struct.random_method()$0
+ test_struct.random_method();$0
}
"#,
);
@@ -419,7 +419,7 @@ impl foo::TestTrait for fundamental::Box<TestStruct> {
fn main() {
let t = fundamental::Box(TestStruct);
- t.some_method()$0
+ t.some_method();$0
}
"#,
);
@@ -466,7 +466,7 @@ impl foo::TestTrait for &TestStruct {
fn main() {
let t = &TestStruct;
- t.some_method()$0
+ t.some_method();$0
}
"#,
);
@@ -507,7 +507,7 @@ fn completion<T: Wrapper>(whatever: T) {
use foo::{NotInScope, Wrapper};
fn completion<T: Wrapper>(whatever: T) {
- whatever.inner().not_in_scope()$0
+ whatever.inner().not_in_scope();$0
}
"#,
);
@@ -579,7 +579,7 @@ fn main() {
use dep::test_mod::TestTrait;
fn main() {
- dep::test_mod::TestAlias::random_method()$0
+ dep::test_mod::TestAlias::random_method();$0
}
"#,
);
@@ -702,7 +702,7 @@ fn main() {
use dep::test_mod::TestTrait;
fn main() {
- dep::test_mod::TestStruct::another_function()$0
+ dep::test_mod::TestStruct::another_function();$0
}
"#,
);
diff --git a/crates/ide-completion/src/tests/raw_identifiers.rs b/crates/ide-completion/src/tests/raw_identifiers.rs
index bc630189ed..d81b3d697a 100644
--- a/crates/ide-completion/src/tests/raw_identifiers.rs
+++ b/crates/ide-completion/src/tests/raw_identifiers.rs
@@ -30,7 +30,7 @@ fn foo() {
"#,
expect![[r#"
fn foo() {
- a::dyn()$0
+ a::dyn();$0
"#]],
);
@@ -45,7 +45,7 @@ fn foo() {
"#,
expect![[r#"
fn foo() {
- a::dyn()$0
+ a::dyn();$0
"#]],
);
}
@@ -63,7 +63,7 @@ fn foo() {
"#,
expect![[r#"
fn foo() {
- a::r#dyn()$0
+ a::r#dyn();$0
"#]],
);
@@ -78,7 +78,7 @@ fn foo() {
"#,
expect![[r#"
fn foo() {
- a::r#dyn()$0
+ a::r#dyn();$0
"#]],
);
}
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index 2ae7d37889..508f6248dd 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -63,7 +63,7 @@ fn _alpha() {}
"#,
r#"
fn main() {
- _alpha()$0
+ _alpha();$0
}
fn _alpha() {}
"#,
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index e4e8c9c6d9..35be6d0cf7 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -420,6 +420,10 @@ config_data! {
assist_termSearch_fuel: usize = 1800,
+ /// Whether to automatically add a semicolon when completing unit-returning functions.
+ ///
+ /// In `match` arms it completes a comma instead.
+ completion_addSemicolonToUnit: bool = true,
/// Toggles the additional completions that automatically add imports when completed.
/// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.
completion_autoimport_enable: bool = true,
@@ -1441,6 +1445,7 @@ impl Config {
CallableCompletionDef::AddParentheses => Some(CallableSnippets::AddParentheses),
CallableCompletionDef::None => None,
},
+ add_semicolon_to_unit: *self.completion_addSemicolonToUnit(source_root),
snippet_cap: SnippetCap::new(self.completion_snippet()),
insert_use: self.insert_use_config(source_root),
prefer_no_std: self.imports_preferNoStd(source_root).to_owned(),
diff --git a/crates/rust-analyzer/src/integrated_benchmarks.rs b/crates/rust-analyzer/src/integrated_benchmarks.rs
index 28f4b809d6..118469df73 100644
--- a/crates/rust-analyzer/src/integrated_benchmarks.rs
+++ b/crates/rust-analyzer/src/integrated_benchmarks.rs
@@ -167,6 +167,7 @@ fn integrated_completion_benchmark() {
prefer_absolute: false,
snippets: Vec::new(),
limit: None,
+ add_semicolon_to_unit: true,
};
let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -213,6 +214,7 @@ fn integrated_completion_benchmark() {
prefer_absolute: false,
snippets: Vec::new(),
limit: None,
+ add_semicolon_to_unit: true,
};
let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
@@ -257,6 +259,7 @@ fn integrated_completion_benchmark() {
prefer_absolute: false,
snippets: Vec::new(),
limit: None,
+ add_semicolon_to_unit: true,
};
let position =
FilePosition { file_id, offset: TextSize::try_from(completion_offset).unwrap() };
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc
index 4fcf580e75..f37fd7f4ab 100644
--- a/docs/user/generated_config.adoc
+++ b/docs/user/generated_config.adoc
@@ -261,6 +261,13 @@ Aliased as `"checkOnSave.targets"`.
Whether `--workspace` should be passed to `cargo check`.
If false, `-p <package>` will be passed instead.
--
+[[rust-analyzer.completion.addSemicolonToUnit]]rust-analyzer.completion.addSemicolonToUnit (default: `true`)::
++
+--
+Whether to automatically add a semicolon when completing unit-returning functions.
+
+In `match` arms it completes a comma instead.
+--
[[rust-analyzer.completion.autoimport.enable]]rust-analyzer.completion.autoimport.enable (default: `true`)::
+
--
diff --git a/editors/code/package.json b/editors/code/package.json
index 0b029460b3..b66f0a64d5 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -1030,6 +1030,16 @@
{
"title": "completion",
"properties": {
+ "rust-analyzer.completion.addSemicolonToUnit": {
+ "markdownDescription": "Whether to automatically add a semicolon when completing unit-returning functions.\n\nIn `match` arms it completes a comma instead.",
+ "default": true,
+ "type": "boolean"
+ }
+ }
+ },
+ {
+ "title": "completion",
+ "properties": {
"rust-analyzer.completion.autoimport.enable": {
"markdownDescription": "Toggles the additional completions that automatically add imports when completed.\nNote that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled.",
"default": true,