Repository: intellij-rust/intellij-rust
Issue: Bug in autocompletion insertion

<!--
Hello and thank you for the issue!
If you would like to report a bug, we have added some points below that you can fill out.
Feel free to remove all the irrelevant text to request a new feature.
-->

## Environment

* **IntelliJ Rust plugin version:**
    0.3.126.3220
* **Rust toolchain version:**
    1.43
* **IDE name and version:**
    intellij 2020.1 community
* **Operating system:**
    linux

## Problem description
Two parenthesis pairs are inserted when autocompleting

## Steps to reproduce

* Type a full completion result

* Press Ctrl-space

* Press opening paren

Video demonstrates the bug:

https://drive.google.com/file/d/1ZWWWkpr23Cu_iKAiCl4lKON8r_dKlCOc/view?usp=sharing


Changed files:
src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt, src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt, src/test/kotlin/org/rust/lang/core/completion/RsCompletionTest.kt, src/main/kotlin/org/rust/lang/core/completion/LookupElements.kt

diff --git a/src/main/kotlin/org/rust/lang/core/completion/LookupElements.kt b/src/main/kotlin/org/rust/lang/core/completion/LookupElements.kt
index 26df4621d..6a8c2bdf2 100644
--- a/src/main/kotlin/org/rust/lang/core/completion/LookupElements.kt
+++ b/src/main/kotlin/org/rust/lang/core/completion/LookupElements.kt
@@ -13,6 +13,7 @@ import com.intellij.codeInsight.lookup.LookupElement
 import com.intellij.codeInsight.lookup.LookupElementBuilder
 import com.intellij.openapi.editor.Document
 import com.intellij.openapi.editor.EditorModificationUtil
+import com.intellij.openapiext.Testmark
 import com.intellij.psi.PsiElement
 import com.intellij.psi.util.PsiTreeUtil
 import org.rust.ide.icons.RsIcons
@@ -250,6 +251,7 @@ open class RsDefaultInsertHandler : InsertHandler<LookupElement> {
                     val isMethodCall = context.getElementOfType<RsMethodOrField>() != null
                     if (!context.alreadyHasCallParens) {
                         document.insertString(context.selectionEndOffset, "()")
+                        context.doNotAddOpenParenCompletionChar()
                     }
                     val caretShift = if (element.valueParameters.isEmpty() && (isMethodCall || !element.hasSelfParameters)) 2 else 1
                     EditorModificationUtil.moveCaretRelatively(context.editor, caretShift)
@@ -264,7 +266,10 @@ open class RsDefaultInsertHandler : InsertHandler<LookupElement> {
                     // Currently this works only for enum variants (and not for structs). It's because in the case of
                     // struct you may want to append an associated function call after a struct name (e.g. `::new()`)
                     val (text, shift) = when {
-                        element.tupleFields != null -> Pair("()", 1)
+                        element.tupleFields != null -> {
+                            context.doNotAddOpenParenCompletionChar()
+                            Pair("()", 1)
+                        }
                         element.blockFields != null -> Pair(" {}", 2)
                         else -> Pair("", 0)
                     }
@@ -335,6 +340,7 @@ private fun addGenericTypeCompletion(element: RsGenericDeclaration, document: Do
     if (element.isFnLikeTrait) {
         if (!context.alreadyHasCallParens) {
             document.insertString(context.selectionEndOffset, "()")
+            context.doNotAddOpenParenCompletionChar()
         }
     } else {
         if (!context.alreadyHasAngleBrackets) {
@@ -345,6 +351,16 @@ private fun addGenericTypeCompletion(element: RsGenericDeclaration, document: Do
     EditorModificationUtil.moveCaretRelatively(context.editor, 1)
 }
 
+// When a user types `(` while completion,
+// `com.intellij.codeInsight.completion.DefaultCharFilter` invokes completion with selected item.
+// And if we insert `()` for the item (for example, function), a user get double parentheses
+private fun InsertionContext.doNotAddOpenParenCompletionChar() {
+    if (completionChar == '(') {
+        setAddCompletionChar(false)
+        Testmarks.doNotAddOpenParenCompletionChar.hit()
+    }
+}
+
 inline fun <reified T : PsiElement> InsertionContext.getElementOfType(strict: Boolean = false): T? =
     PsiTreeUtil.findElementOfClassAtOffset(file, tailOffset - 1, T::class.java, strict)
 
@@ -410,3 +426,7 @@ private fun isCompatibleTypes(lookup: ImplLookup, actualTy: Ty?, expectedTy: Ty?
 
     return lookup.ctx.combineTypesNoVars(actualTy.foldWith(folder), expectedTy.foldWith(folder)).isOk
 }
+
+object Testmarks {
+    val doNotAddOpenParenCompletionChar = Testmark("doNotAddOpenParenCompletionChar")
+}
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTest.kt b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTest.kt
index 80d7eba4d..3c2c633fd 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTest.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTest.kt
@@ -1237,4 +1237,45 @@ class RsCompletionTest : RsCompletionTestBase() {
     """, """
         fn foo(f: &FnOnce(/*caret*/)) {}
     """)
+
+    fun `test do not insert second parenthesis 1`() = checkCompletion("foo", """
+        fn foo() {}
+        fn foo2() {}
+        fn main() {
+            foo/*caret*/
+        }
+    """, """
+        fn foo() {}
+        fn foo2() {}
+        fn main() {
+            foo()/*caret*/
+        }
+    """, completionChar = '(', testmark = Testmarks.doNotAddOpenParenCompletionChar)
+
+    fun `test do not insert second parenthesis 2`() = checkCompletion("V1", """
+        enum E {
+            V1(i32),
+            V2(i32)
+        }
+        fn main() {
+            E::V/*caret*/
+        }
+    """, """
+        enum E {
+            V1(i32),
+            V2(i32)
+        }
+        fn main() {
+            E::V1(/*caret*/)
+        }
+    """, completionChar = '(', testmark = Testmarks.doNotAddOpenParenCompletionChar)
+
+    @ProjectDescriptor(WithStdlibRustProjectDescriptor::class)
+    fun `test do not insert second parenthesis 3`() = checkCompletion("FnOnce", """
+        struct FnOnceStruct;
+        fn foo(f: FnOnce/*caret*/) {}
+    """, """
+        struct FnOnceStruct;
+        fn foo(f: FnOnce(/*caret*/)) {}
+    """, completionChar = '(', testmark = Testmarks.doNotAddOpenParenCompletionChar)
 }
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
index bd41a1b3b..4cf09e41c 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestBase.kt
@@ -5,6 +5,7 @@
 
 package org.rust.lang.core.completion
 
+import com.intellij.openapiext.Testmark
 import com.intellij.psi.PsiElement
 import com.intellij.psi.util.PsiTreeUtil
 import org.intellij.lang.annotations.Language
@@ -75,8 +76,10 @@ abstract class RsCompletionTestBase : RsTestBase() {
     protected fun checkCompletion(
         lookupString: String,
         @Language("Rust") before: String,
-        @Language("Rust") after: String
-    ) = completionFixture.checkCompletion(lookupString, before, after)
+        @Language("Rust") after: String,
+        completionChar: Char = '\\n',
+        testmark: Testmark? = null
+    ) = completionFixture.checkCompletion(lookupString, before, after, completionChar, testmark)
 
     protected fun checkNotContainsCompletion(
         variant: String,
diff --git a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
index d7bcd7e64..75df35b0c 100644
--- a/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
+++ b/src/test/kotlin/org/rust/lang/core/completion/RsCompletionTestFixtureBase.kt
@@ -8,6 +8,7 @@ package org.rust.lang.core.completion
 import com.intellij.codeInsight.lookup.LookupElement
 import com.intellij.openapi.project.Project
 import com.intellij.openapi.vfs.VirtualFileFilter
+import com.intellij.openapiext.Testmark
 import com.intellij.psi.impl.PsiManagerEx
 import com.intellij.testFramework.fixtures.CodeInsightTestFixture
 import com.intellij.testFramework.fixtures.impl.BaseFixture
@@ -66,13 +67,20 @@ abstract class RsCompletionTestFixtureBase<IN>(
     fun checkCompletion(
         lookupString: String,
         before: IN,
-        @Language("Rust") after: String
-    ) = checkByText(before, after.trimIndent()) {
-        val items = myFixture.completeBasic()
-            ?: return@checkByText // single completion was inserted
-        val lookupItem = items.find { it.lookupString == lookupString } ?: return@checkByText
-        myFixture.lookup.currentItem = lookupItem
-        myFixture.type('\\n')
+        @Language("Rust") after: String,
+        completionChar: Char,
+        testmark: Testmark?
+    ) {
+        val action = {
+            checkByText(before, after.trimIndent()) {
+                val items = myFixture.completeBasic()
+                    ?: return@checkByText // single completion was inserted
+                val lookupItem = items.find { it.lookupString == lookupString } ?: return@checkByText
+                myFixture.lookup.currentItem = lookupItem
+                myFixture.type(completionChar)
+            }
+        }
+        testmark?.checkHit(action)
     }
 
     fun checkNoCompletion(code: IN) {