Repository: intellij-rust/intellij-rust
Issue: IRREFUTABLE_LET_PATTERNS classifed as experimental in rust 1.32

<!--
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.92.2548-183-nightly**
* **Rust toolchain version: 1.32.0**
* **IDE name and version: CLion 2018.3.4**
* **Operating system: Linux**

## Problem description

Pull request 3337 changed the required rust version for IRREFUTABLE_LET_PATTERNS from 1.27 to 1.33 causing working code to report an error on rust 1.32. 

## Steps to reproduce
The code below reports an error with the plug-in but compiles on rust 1.32
```rust
fn test() {
    let result: Result<String, Box<Error>> = Result::Ok("test".to_string());
    if let Ok(st) = result {}
 }
```

Changed files:
src/main/kotlin/org/rust/ide/intentions/MatchToIfLetIntention.kt, src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt, src/main/kotlin/org/rust/ide/intentions/IfLetToMatchIntention.kt, src/main/kotlin/org/rust/lang/core/psi/ext/RsPat.kt, src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt

diff --git a/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt b/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
index 80550846a..8eb05c539 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
@@ -369,7 +369,7 @@ class RsErrorAnnotator : RsAnnotatorBase(), HighlightRangeExtension {
 
     private fun checkCondition(holder: AnnotationHolder, element: RsCondition) {
         val pat = element.pat ?: return
-        if (pat.skipUnnecessaryTupDown().isIrrefutable) {
+        if (pat.isIrrefutable) {
             checkFeature(holder, pat, IRREFUTABLE_LET_PATTERNS, "irrefutable let pattern")
         }
     }
diff --git a/src/main/kotlin/org/rust/ide/intentions/IfLetToMatchIntention.kt b/src/main/kotlin/org/rust/ide/intentions/IfLetToMatchIntention.kt
index abaef4595..0f1300431 100644
--- a/src/main/kotlin/org/rust/ide/intentions/IfLetToMatchIntention.kt
+++ b/src/main/kotlin/org/rust/ide/intentions/IfLetToMatchIntention.kt
@@ -10,8 +10,10 @@ import com.intellij.openapi.project.Project
 import com.intellij.psi.PsiElement
 import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.*
+import org.rust.lang.core.resolve.knownItems
 import org.rust.lang.core.types.ty.TyAdt
 import org.rust.lang.core.types.type
+import org.rust.stdext.mapToSet
 
 class IfLetToMatchIntention : RsElementBaseIntentionAction<IfLetToMatchIntention.Context>() {
     override fun getText(): String = "Convert if let statement to match"
@@ -50,7 +52,7 @@ class IfLetToMatchIntention : RsElementBaseIntentionAction<IfLetToMatchIntention
     override fun invoke(project: Project, editor: Editor, ctx: Context) {
         val (ifStmt, target, matchArms, elseBody) = ctx
         val item = (target.type as? TyAdt)?.item as? RsEnumItem
-
+        val optionOrResultPats = matchArms.map(MatchArm::matchArm).filter(RsPat::isPossibleOptionOrResultVariant)
         val generatedCode = buildString {
             append("match ")
             append(target.text)
@@ -61,9 +63,9 @@ class IfLetToMatchIntention : RsElementBaseIntentionAction<IfLetToMatchIntention
                 append(" => ")
                 append(it.body.text)
             }
-            if (elseBody != null || !isExhaustiveOptionOrResult(item, matchArms)) {
+            if (elseBody != null || item == null || !allOptionOrResultVariantsCovered(item, optionOrResultPats)) {
                 append('\\n')
-                append(missingBranch(item, matchArms))
+                append(missingBranch(item, optionOrResultPats))
                 append(" => ")
                 append(elseBody?.text ?: "{}")
             }
@@ -74,16 +76,10 @@ class IfLetToMatchIntention : RsElementBaseIntentionAction<IfLetToMatchIntention
         ifStmt.replace(matchExpression)
     }
 
-    private fun isExhaustiveOptionOrResult(item: RsEnumItem?, arms: List<MatchArm>): Boolean {
-        val patterns = arms.map(MatchArm::matchArm).filter(RsPat::isIrrefutable)
-        return matchStdOptionOrResult(item, patterns, true)
-    }
-
-    private fun missingBranch(item: RsEnumItem?, arms: List<MatchArm>): String {
-        if (item == null || !item.isStdOptionOrResult || arms.size != 1) return "_"
-        val pat = arms.first().matchArm.skipUnnecessaryTupDown()
-        if (!pat.isIrrefutable) return "_"
-        return when (pat.text.substringBefore('(')) {
+    private fun missingBranch(item: RsEnumItem?, pats: List<RsPat>): String {
+        if (item == null || !item.isStdOptionOrResult) return "_"
+        val patName = pats.singleOrNull()?.name ?: return "_"
+        return when (patName) {
             "Some" -> "None"
             "Ok" -> "Err(..)"
             "Err" -> "Ok(..)"
@@ -147,4 +143,23 @@ class IfLetToMatchIntention : RsElementBaseIntentionAction<IfLetToMatchIntention
 
         return context
     }
+
+    companion object {
+        private fun allOptionOrResultVariantsCovered(item: RsEnumItem?, pats: List<RsPat>): Boolean {
+            if (item == null || !item.isStdOptionOrResult || pats.size < 2) return false
+            val patNames = pats.mapToSet(RsPat::name)
+            val allVariantNames = if (item == item.knownItems.Option) {
+                setOf("Some", "None")
+            } else {
+                setOf("Ok", "Err")
+            }
+            return patNames == allVariantNames
+        }
+    }
 }
+
+private val RsPat.name: String
+    get() = skipUnnecessaryTupDown().text.substringBefore('(')
+
+private val RsPat.isPossibleOptionOrResultVariant: Boolean
+    get() = name == "None" || descendantOfTypeStrict<RsPat>()?.isIrrefutable == true
diff --git a/src/main/kotlin/org/rust/ide/intentions/MatchToIfLetIntention.kt b/src/main/kotlin/org/rust/ide/intentions/MatchToIfLetIntention.kt
index 14ab54195..dc796a62d 100644
--- a/src/main/kotlin/org/rust/ide/intentions/MatchToIfLetIntention.kt
+++ b/src/main/kotlin/org/rust/ide/intentions/MatchToIfLetIntention.kt
@@ -12,9 +12,6 @@ import org.rust.lang.core.psi.*
 import org.rust.lang.core.psi.ext.ancestorStrict
 import org.rust.lang.core.psi.ext.getNextNonCommentSibling
 import org.rust.lang.core.psi.ext.isIrrefutable
-import org.rust.lang.core.psi.ext.matchStdOptionOrResult
-import org.rust.lang.core.types.ty.TyAdt
-import org.rust.lang.core.types.type
 
 class MatchToIfLetIntention : RsElementBaseIntentionAction<MatchToIfLetIntention.Context>() {
     override fun getText() = "Convert match statement to if let"
@@ -35,10 +32,8 @@ class MatchToIfLetIntention : RsElementBaseIntentionAction<MatchToIfLetIntention
 
         val nonVoidArm = matchArmList.singleOrNull { it.expr?.isVoid == false } ?: return null
         if (nonVoidArm.matchArmGuard != null || nonVoidArm.outerAttrList.isNotEmpty()) return null
-
         val pattern = nonVoidArm.patList.singleOrNull() ?: return null
-        val item = (matchTarget.type as? TyAdt)?.item as? RsEnumItem
-        if (!isRefutablePattern(item, pattern)) return null
+        if (pattern.isIrrefutable) return null
 
         return Context(matchExpr, matchTarget, nonVoidArm, pattern)
     }
@@ -61,5 +56,3 @@ class MatchToIfLetIntention : RsElementBaseIntentionAction<MatchToIfLetIntention
         get() = (this is RsBlockExpr && block.lbrace.getNextNonCommentSibling() == block.rbrace)
             || this is RsUnitExpr
 }
-
-private fun isRefutablePattern(item: RsEnumItem?, pattern: RsPat): Boolean = !pattern.isIrrefutable || matchStdOptionOrResult(item, listOf(pattern))
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsPat.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsPat.kt
index 8c76b5567..1bc3d067d 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsPat.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsPat.kt
@@ -6,24 +6,40 @@
 package org.rust.lang.core.psi.ext
 
 import org.rust.lang.core.psi.*
-import org.rust.lang.core.resolve.knownItems
+import org.rust.lang.core.resolve.ref.deepResolve
 
 val RsPat.isIrrefutable: Boolean
-    get() = when (this) {
-        is RsPatTupleStruct -> patList.all { it.isIrrefutable }
-        is RsPatSlice -> patList.all { it.isIrrefutable }
-        is RsPatTup -> patList.all { it.isIrrefutable }
-        is RsPatBox -> pat.isIrrefutable
-        is RsPatRef -> pat.isIrrefutable
-        is RsPatStruct -> {
-            val canBeIrrefutable = when (val item = path.reference.resolve()) {
-                is RsStructItem -> true
-                is RsEnumVariant -> item.parentEnum.enumBody?.enumVariantList?.size == 1
-                else -> false
-            }
-            canBeIrrefutable && patFieldList.all { it.pat?.isIrrefutable ?: (it.patBinding != null) }
-        }
-        is RsPatConst, is RsPatRange -> false
+    get() = when (val pat = skipUnnecessaryTupDown()) {
+        is RsPatSlice ->
+            pat.patList.all { it.isIrrefutable }
+        is RsPatTup ->
+            pat.patList.all { it.isIrrefutable }
+        is RsPatBox ->
+            pat.pat.isIrrefutable
+        is RsPatRef ->
+            pat.pat.isIrrefutable
+        is RsPatStruct ->
+            pat.path.isIrrefutable && pat.patFieldList.all { it.pat?.isIrrefutable ?: (it.patBinding != null) }
+        is RsPatTupleStruct ->
+            pat.path.isIrrefutable && pat.patList.all { it.isIrrefutable }
+        is RsPatIdent ->
+            pat.patBinding.isIrrefutable
+        is RsPatConst, is RsPatRange ->
+            false
+        else ->
+            true
+    }
+
+private val RsPath.isIrrefutable: Boolean
+    get() = when (val item = reference.deepResolve()) {
+        is RsStructItem -> true
+        is RsEnumVariant -> item.parentEnum.enumBody?.enumVariantList?.size == 1
+        else -> false
+    }
+
+private val RsPatBinding.isIrrefutable: Boolean
+    get() = when (reference.resolve()) {
+        is RsStructItem, is RsEnumVariant -> false
         else -> true
     }
 
@@ -34,15 +50,3 @@ fun RsPat.skipUnnecessaryTupDown(): RsPat {
     }
     return pat
 }
-
-fun matchStdOptionOrResult(item: RsEnumItem?, patterns: List<RsPat>, allVariants: Boolean = false): Boolean {
-    if (patterns.isEmpty() || item?.isStdOptionOrResult != true) return false
-    val boolOp = if (allVariants) Boolean::and else Boolean::or
-    return patterns.map { it.skipUnnecessaryTupDown().text.substringBefore('(') }.let {
-        if (item == item!!.knownItems.Option) {
-            boolOp("Some" in it, "None" in it)
-        } else {
-            boolOp("Ok" in it, "Err" in it)
-        }
-    }
-}
diff --git a/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt b/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
index ed8d417f3..898aabc23 100644
--- a/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
+++ b/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
@@ -1447,20 +1447,33 @@ class RsErrorAnnotatorTest : RsAnnotatorTestBase(RsErrorAnnotator::class.java) {
     """)
 
     @MockRustcVersion("1.31.0")
-    fun `test irrefutable let pattern E0658 for struct literals`() = checkErrors("""
+    fun `test irrefutable let pattern E0658 for struct literals 1`() = checkErrors("""
         struct S { x: i32 }
-        enum E1 {
-            V { x: i32 }
-        }
-        enum E2 {
-            A,
-            V { x: i32 }
-        }
-        fn foo(a: S, b: E1, c: E2, d: Unknown) {
+        struct ST(i32);
+        enum E1 { V { x: i32 } }
+        enum E2 { A, V { x: i32 } }
+        fn foo(a: S, b: ST, c: E1, d: E2, e: Unknown) {
             if let <error descr="irrefutable let pattern is experimental [E0658]">S { x }</error> = a {}
-            if let <error descr="irrefutable let pattern is experimental [E0658]">E1::V { x }</error> = b {}
-            if let E2::V { x } = c {}
-            if let Unknown { x } = d {}
+            if let <error descr="irrefutable let pattern is experimental [E0658]">S(x)</error> = b {}
+            if let <error descr="irrefutable let pattern is experimental [E0658]">E1::V { x }</error> = c {}
+            if let E2::V { x } = d {}
+            if let Unknown { x } = e {}
+        }
+    """)
+
+    @MockRustcVersion("1.32.0-nightly")
+    fun `test irrefutable let pattern E0658 for struct literals 2`() = checkErrors("""
+        #![feature(irrefutable_let_patterns)]
+        struct S { x: i32 }
+        struct ST(i32);
+        enum E1 { V { x: i32 } }
+        enum E2 { A, V { x: i32 } }
+        fn foo(a: S, b: ST, c: E1, d: E2, e: Unknown) {
+            if let S { x } = a {}
+            if let S(x) = b {}
+            if let E1::V { x } = c {}
+            if let E2::V { x } = d {}
+            if let Unknown { x } = e {}
         }
     """)
 }