Repository: intellij-rust/intellij-rust
Issue: Converting struct to tuple doesn't convert struct creation via Self

<!--
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.127.3249-202
* **Rust toolchain version:** 1.4.5.0
* **IDE name and version:** CLion 2020.2
* **Operating system:** Linux

## Problem description
The "convert to tuple" intention doesn't refactor construction.

## Steps to reproduce
```rust
struct MyStruct {
    a: i32,
    b: i32,
}
impl MyStruct {
    fn new(a: i32, b: i32) -> Self {
        Self {a,b}
    }

    fn destructure(self) -> (i32, i32) {
        let MyStruct{a,b} = self;
        (a,b)
    }
}
```
Running "convert to tuple" intention gives the following:

```rust

struct MyStruct(i32, i32);

impl MyStruct {
    fn new(a: i32, b: i32) -> Self {
        Self {a,b} // <------ not converted! it should become `Self(a, b)`
    }

    fn destructure(self) -> (i32, i32) {
        let MyStruct(a, b) = self;
        (a,b)
    }
}
```
<!--
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/convertStruct/RsConvertToNamedFieldsProcessor.kt
src/main/kotlin/org/rust/lang/core/psi/ext/RsElement.kt
src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToTupleProcessor.kt

diff --git a/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToNamedFieldsProcessor.kt b/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToNamedFieldsProcessor.kt
index 9770aa87d..2cfa41d2d 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToNamedFieldsProcessor.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToNamedFieldsProcessor.kt
@@ -37,8 +37,7 @@ class RsConvertToNamedFieldsProcessor(
 
     override fun findUsages(): Array<UsageInfo> {
         if (!convertUsages) return arrayOf()
-        var usages = ReferencesSearch
-            .search(element)
+        var usages = element.searchReferencesWithSelf()
             .asSequence()
             .map { MyUsageInfo(it) }
 
@@ -162,3 +161,4 @@ class RsConvertToNamedFieldsProcessor(
 
     override fun getRefactoringId(): String = "refactoring.convertToNamedFields"
 }
+
diff --git a/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToTupleProcessor.kt b/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToTupleProcessor.kt
index 421526fdb..89926ef76 100644
--- a/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToTupleProcessor.kt
+++ b/src/main/kotlin/org/rust/ide/refactoring/convertStruct/RsConvertToTupleProcessor.kt
@@ -28,8 +28,7 @@ class RsConvertToTupleProcessor(
     override fun findUsages(): Array<UsageInfo> {
         if (!convertUsages) return arrayOf()
 
-        var usages = ReferencesSearch
-            .search(element)
+        var usages = element.searchReferencesWithSelf()
             .asSequence()
             .map { UsageInfo(it) }
 
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsElement.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsElement.kt
index 4b0e9655f..f074b2583 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsElement.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsElement.kt
@@ -9,11 +9,12 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement
 import com.intellij.extapi.psi.StubBasedPsiElementBase
 import com.intellij.lang.ASTNode
 import com.intellij.psi.*
-import com.intellij.psi.search.SearchScope
+import com.intellij.psi.search.*
 import com.intellij.psi.search.searches.ReferencesSearch
 import com.intellij.psi.stubs.IStubElementType
 import com.intellij.psi.stubs.StubElement
 import com.intellij.util.Query
+import com.intellij.util.containers.addIfNotNull
 import org.rust.cargo.project.model.CargoProject
 import org.rust.cargo.project.model.cargoProjects
 import org.rust.cargo.project.workspace.CargoWorkspace
@@ -216,3 +217,45 @@ fun RsElement.searchReferences(scope: SearchScope? = null): Query<PsiReference>
         ReferencesSearch.search(this, scope)
     }
 }
+
+/**
+ * struct Foo {}
+ * ~~~~~~~~~~~~~ this
+ * impl Foo {
+ *     fn new() -> Self { ... }
+ *                 ~~~~ this resolves to impl, so usual [searchReferences] will not find it
+ * }
+ */
+fun RsElement.searchReferencesWithSelf(): List<PsiReference> {
+    val references = ReferencesSearch.search(this).toMutableList()
+    val searchHelper = PsiSearchHelper.getInstance(project)
+    references += references.flatMap {
+        it.findSelfReferences(searchHelper) ?: emptyList()
+    }
+    return references
+}
+
+/**
+ * impl Foo {
+ *      ~~~~ this
+ *     fn new() -> Self { ... }
+ *                 ~~~~ result
+ * }
+ */
+private fun PsiReference.findSelfReferences(searchHelper: PsiSearchHelper): List<PsiReference>? {
+    val implPath = element as? RsPath ?: return null
+    val implType = implPath.parent as? RsPathType ?: return null
+    val impl = implType.parent as? RsImplItem ?: return null
+    if (impl.typeReference != implType) return null
+
+    val result = mutableListOf<PsiReference>()
+    val processor = TextOccurenceProcessor run@{ element, offset ->
+        if (offset != 0) return@run true
+        val path = (element.parent as? RsPath)?.takeIf { it.cself == element } ?: return@run true
+        result.addIfNotNull(path.reference.takeIf { it?.resolve() == impl })
+        true
+    }
+    val searchScope = LocalSearchScope(impl)
+    searchHelper.processElementsWithWord(processor, searchScope, "Self", UsageSearchContext.IN_CODE, true, false)
+    return result
+}
diff --git a/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt b/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
index 59f6c20f3..ed420dd7c 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/ConvertToNamedFieldsRefactoringTest.kt
@@ -211,6 +211,25 @@ class ConvertToNamedFieldsRefactoringTest : RsTestBase() {
         }
     """)
 
+    fun `test convert Self in impls`() = doAvailableTest("""
+        struct Test/*caret*/(i32);
+        impl Test {
+            fn new() -> Self {
+                Self(0)
+            }
+        }
+    """, """
+        struct Test {
+            _0: i32,
+        }
+
+        impl Test {
+            fn new() -> Self {
+                Self { _0: 0 }
+            }
+        }
+    """)
+
     private fun doAvailableTest(@Language("Rust") before: String, @Language("Rust") after: String) {
         InlineFile(before.trimIndent()).withCaret()
         myFixture.launchAction("Rust.RsConvertToNamedFields")
diff --git a/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt b/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
index 00a42edc9..426abf206 100644
--- a/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
+++ b/src/test/kotlin/org/rust/ide/refactoring/ConvertToTupleRefactoringTest.kt
@@ -169,6 +169,25 @@ class ConvertToTupleRefactoringTest : RsTestBase() {
                 T2: Trait;
     """)
 
+    fun `test replace Self in impls`() = doAvailableTest("""
+        struct Test/*caret*/ { a: i32, b: i32 }
+        impl Test {
+            fn new() -> Self {
+                let b = 1;
+                Self { a: 0, b }
+            }
+        }
+    """, """
+        struct Test(i32, i32);
+
+        impl Test {
+            fn new() -> Self {
+                let b = 1;
+                Self(0, b)
+            }
+        }
+    """)
+
     private fun doAvailableTest(@Language("Rust") before: String, @Language("Rust") after: String) {
         InlineFile(before.trimIndent()).withCaret()
         myFixture.launchAction("Rust.RsConvertToTuple")