Repository: intellij-rust/intellij-rust
Issue: Wrong codegen with `extern fn` as function parameter

<!--
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:** v0.2.96.2123-183
* **Rust toolchain version:** 1.35.0-nightly
* **IDE name and version:** CLion 183.5429.37
* **Operating system:** Linux x64

## Problem description
When generating code for a `struct` `impl` through "Implement members",
`extern fn`s (i.e., function pointers) are not marked `extern`.

## Steps to reproduce
Use this code:
```rust
trait WithFunctionPtr {
    fn call(handler: extern fn(flag: bool));
}

struct UsingFunctionPtr;
impl WithFunctionPtr for UsingFunctionPtr {
    /* The line will have an error, use Alt+Enter > Implement members */
}
```
This generates:
```rust
fn call(handler: fn(bool)) {}
```
while it should be:
```rust
fn call(handler: extern fn(bool)) {}
```

<!--
Please include as much of your codebase as needed to reproduce the error.
If the relevant files are large, please provide a link to a public repository or a [Gist](https://gist.github.com/).
-->


Changed files:
src/main/kotlin/org/rust/ide/refactoring/implementMembers/impl.kt
src/test/kotlin/org/rust/ide/refactoring/implementMembers/ImplementMembersHandlerTest.kt

diff --git a/src/main/kotlin/org/rust/ide/refactoring/implementMembers/impl.kt b/src/main/kotlin/org/rust/ide/refactoring/implementMembers/impl.kt
index 9d50d3765..84cb0d2bd 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/implementMembers/impl.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/implementMembers/impl.kt
@@ -12,6 +12,7 @@ import com.intellij.openapi.editor.ScrollType
 import com.intellij.psi.PsiElement
 import com.intellij.psi.PsiWhiteSpace
 import org.rust.ide.inspections.import.RsImportHelper.importTypeReferencesFromElements
+import org.rust.ide.presentation.shortPresentableText
 import org.rust.lang.core.macros.expandedFromRecursively
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
@@ -41,7 +42,7 @@ fun generateTraitMembers(impl: RsImplItem, editor: Editor?) {
     if (chosen.isEmpty()) return
     runWriteAction {
         // Non-null was checked by `findMembersToImplement`.
-        insertNewTraitMembers(chosen, impl.members!!, trait, editor)
+        insertNewTraitMembers(chosen, impl, trait, editor)
     }
 }
 
@@ -56,20 +57,21 @@ private fun findMembersToImplement(impl: RsImplItem): Pair<TraitImplementationIn
 
 private fun insertNewTraitMembers(
     selected: Collection<RsAbstractable>,
-    existingMembers: RsMembers,
+    impl: RsImplItem,
     trait: BoundElement<RsTraitItem>,
     editor: Editor?
 ) {
     checkWriteAccessAllowed()
     if (selected.isEmpty()) return
 
-    val templateImpl = RsPsiFactory(existingMembers.project).createTraitMembers(selected, trait.subst)
+    val templateImpl = RsPsiFactory(impl.project).createTraitMembers(selected, trait, impl)
 
     val traitMembers = trait.element.expandedMembers
     val newMembers = templateImpl.childrenOfType<RsAbstractable>()
 
     // [1] First, check if the order of the existingMembers already implemented
     // matches the order of existingMembers in the trait declaration.
+    val existingMembers = impl.members!!
     val existingMembersWithPosInTrait = existingMembers.expandedMembers.map { existingMember ->
         Pair(existingMember, traitMembers.indexOfFirst {
             it.elementType == existingMember.elementType && it.name == existingMember.name
@@ -151,7 +153,12 @@ private fun createExtraWhitespacesAroundFunction(left: PsiElement, right: PsiEle
     return RsPsiFactory(left.project).createWhitespace("\\n".repeat(extraLineCount))
 }
 
-private fun RsPsiFactory.createTraitMembers(members: Collection<RsAbstractable>, subst: Substitution): RsMembers {
+private fun RsPsiFactory.createTraitMembers(
+    members: Collection<RsAbstractable>,
+    trait: BoundElement<RsTraitItem>,
+    impl: RsImplItem
+): RsMembers {
+    val subst = trait.subst
     val body = members.joinToString(separator = "\\n", transform = {
         when (it) {
             is RsConstant -> {
@@ -162,7 +169,7 @@ private fun RsPsiFactory.createTraitMembers(members: Collection<RsAbstractable>,
             is RsTypeAlias ->
                 "    type ${it.escapedName} = ();"
             is RsFunction ->
-                "    ${it.getSignatureText(subst) ?: ""}{\\n        unimplemented!()\\n    }"
+                "    ${it.getSignatureText(subst, trait, impl) ?: ""}{\\n        unimplemented!()\\n    }"
             else ->
                 error("Unknown trait member")
         }
@@ -171,7 +178,11 @@ private fun RsPsiFactory.createTraitMembers(members: Collection<RsAbstractable>,
     return createMembers(body)
 }
 
-private fun RsFunction.getSignatureText(subst: Substitution): String? {
+private fun RsFunction.getSignatureText(
+    subst: Substitution,
+    trait: BoundElement<RsTraitItem>,
+    impl: RsImplItem
+): String? {
     val async = if (isAsync) "async " else ""
     val unsafe = if (isUnsafe) "unsafe " else ""
     // We can't simply take a substring of original method declaration
@@ -180,9 +191,22 @@ private fun RsFunction.getSignatureText(subst: Substitution): String? {
     val generics = typeParameterList?.text ?: ""
 
     val selfArgument = listOfNotNull(selfParameter?.substAndGetText(subst))
+    val typeParameters = trait.element.typeParameters.mapIndexed { index, typeParameter ->
+        typeParameter.identifier.text to (index to typeParameter)
+    }.toMap()
+    val typeArguments = impl.traitRef?.path?.typeArguments.orEmpty()
     val valueArguments = valueParameters.map {
+        val fnPointerType= when (val typeReference = it.typeReference) {
+            is RsFnPointerType -> typeReference
+            is RsBaseType -> {
+                val (index, typeParameter) = typeParameters[typeReference.type.shortPresentableText] ?: (-1 to null)
+                (typeArguments.getOrNull(index) ?: typeParameter?.typeReference) as? RsFnPointerType
+            }
+            else -> null
+        }
+        val extern = fnPointerType?.externAbi?.extern?.text?.let { text -> "$text " } ?: ""
         // fix possible anon parameter
-        "${it.pat?.text ?: "_"}: ${it.typeReference?.substAndGetText(subst) ?: "()"}"
+        "${it.pat?.text ?: "_"}: $extern${it.typeReference?.substAndGetText(subst) ?: "()"}"
     }
     val allArguments = selfArgument + valueArguments
 
diff --git a/src/test/kotlin/org/rust/ide/refactoring/implementMembers/ImplementMembersHandlerTest.kt b/src/test/kotlin/org/rust/ide/refactoring/implementMembers/ImplementMembersHandlerTest.kt
index 4322b131e..dbeeafd21 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/implementMembers/ImplementMembersHandlerTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/implementMembers/ImplementMembersHandlerTest.kt
@@ -209,6 +209,86 @@ class ImplementMembersHandlerTest : RsTestBase() {
         }
     """)
 
+    fun `test support extern keyword`() = doTest("""
+        trait T {
+            fn call(handler: extern fn(flag: bool));
+        }
+        struct S;
+        impl T for S {/*caret*/}
+    """, listOf(
+        ImplementMemberSelection("call(handler: extern fn(flag: bool))", true, true)
+    ), """
+        trait T {
+            fn call(handler: extern fn(flag: bool));
+        }
+        struct S;
+        impl T for S {
+            fn call(handler: extern fn(bool)) {
+                <selection>unimplemented!()</selection>
+            }
+        }
+    """)
+
+    fun `test support extern keyword 2`() = doTest("""
+        trait T<X, Y, Z> {
+            fn call(z: Z, x: X, y: Y);
+        }
+        struct S;
+        impl T<fn(), extern fn(), fn(bool)> for S {/*caret*/}
+    """, listOf(
+        ImplementMemberSelection("call(z: Z, x: X, y: Y)", true, true)
+    ), """
+        trait T<X, Y, Z> {
+            fn call(z: Z, x: X, y: Y);
+        }
+        struct S;
+        impl T<fn(), extern fn(), fn(bool)> for S {
+            fn call(z: fn(bool), x: fn(), y: extern fn()) {
+                <selection>unimplemented!()</selection>
+            }
+        }
+    """)
+
+    fun `test support extern keyword 3`() = doTest("""
+        trait T<P = extern fn()> {
+            fn call(handler: P);
+        }
+        struct S;
+        impl T for S {/*caret*/}
+    """, listOf(
+        ImplementMemberSelection("call(handler: P)", true, true)
+    ), """
+        trait T<P = extern fn()> {
+            fn call(handler: P);
+        }
+        struct S;
+        impl T for S {
+            fn call(handler: extern fn()) {
+                <selection>unimplemented!()</selection>
+            }
+        }
+    """)
+
+    fun `test support extern keyword 4`() = doTest("""
+        trait T<P = extern fn()> {
+            fn call(handler: P);
+        }
+        struct S;
+        impl T<extern fn(bool)> for S {/*caret*/}
+    """, listOf(
+        ImplementMemberSelection("call(handler: P)", true, true)
+    ), """
+        trait T<P = extern fn()> {
+            fn call(handler: P);
+        }
+        struct S;
+        impl T<extern fn(bool)> for S {
+            fn call(handler: extern fn(bool)) {
+                <selection>unimplemented!()</selection>
+            }
+        }
+    """)
+
     fun `test implement unsafe methods`() = doTest("""
         trait T {
             unsafe fn f1();