Repository: intellij-rust/intellij-rust
Issue: Demorgan intention may incorrectly omit parens

## Environment

* **IntelliJ Rust plugin version:** 0.4.159.4199-212-nightly
* **Rust toolchain version:** 1.55.0 (c8dfcfe04 2021-09-06) x86_64-unknown-linux-gnu
* **IDE name and version:** CLion 2021.2.3 (CL-212.5457.51)
* **Operating system:** Linux 5.14.14-zen1-1-zen
* **Macro expansion engine:** new
* **Name resolution engine:** new

## Steps to reproduce
```rust
fn func() -> bool {
    a && !(b /*caret*/&& c)
}
```

Expected after applying Demorgan intention:
```rust
fn func() -> bool {
    a && (!b || !c)
}
```

Actual:
```rust
fn func() -> bool {
    a && !b || !c
}
```

Changed files:
src/main/kotlin/org/rust/ide/intentions/DemorgansLawIntention.kt, src/test/kotlin/org/rust/ide/intentions/DemorgansLawIntentionTest.kt

diff --git a/src/main/kotlin/org/rust/ide/intentions/DemorgansLawIntention.kt b/src/main/kotlin/org/rust/ide/intentions/DemorgansLawIntention.kt
index 1179c5435..10a1c04a6 100644
--- a/src/main/kotlin/org/rust/ide/intentions/DemorgansLawIntention.kt
+++ b/src/main/kotlin/org/rust/ide/intentions/DemorgansLawIntention.kt
@@ -71,18 +71,30 @@ class DemorgansLawIntention : RsElementBaseIntentionAction<DemorgansLawIntention
     private fun applyDemorgan(project: Project, topBinExpr: RsBinaryExpr, opType: BinaryOperator) {
         val converted = convertConjunctionExpression(topBinExpr, opType) ?: return
 
-        var expressionToReplace: RsExpr = topBinExpr
-        var expString = "!($converted)"
-        val parent = topBinExpr.parent.parent
-        if (parent.isNegation()) {
-            expressionToReplace = parent as RsExpr
-            expString = converted
+        val parent = topBinExpr.parent?.parent
+        val (expString, expressionToReplace) = if (parent != null && parent.isNegation()) {
+            val grandParent = parent.parent
+            val convertedOpType = if (opType == OR) AND else OR
+            val canOmitParens = grandParent.canOmitParensFor(convertedOpType)
+            val expString = if (canOmitParens) converted else "($converted)"
+            expString to parent as RsExpr
+        } else {
+            "!($converted)" to topBinExpr
         }
         val newExpr = RsPsiFactory(project).createExpression(expString)
 
         expressionToReplace.replace(newExpr)
     }
 
+    private fun PsiElement.canOmitParensFor(opType: LogicOp): Boolean {
+        if (this !is RsBinaryExpr) return true
+        return when (binaryOp.operatorType) {
+            AND -> opType == AND
+            OR -> true
+            else -> false
+        }
+    }
+
     private fun isConjunctionExpression(expression: RsExpr, opType: BinaryOperator): Boolean {
         return expression is RsBinaryExpr && expression.operatorType == opType
     }
diff --git a/src/test/kotlin/org/rust/ide/intentions/DemorgansLawIntentionTest.kt b/src/test/kotlin/org/rust/ide/intentions/DemorgansLawIntentionTest.kt
index 62e79e25a..64d0d9d0f 100644
--- a/src/test/kotlin/org/rust/ide/intentions/DemorgansLawIntentionTest.kt
+++ b/src/test/kotlin/org/rust/ide/intentions/DemorgansLawIntentionTest.kt
@@ -126,4 +126,44 @@ class DemorgansLawIntentionTest : RsIntentionTestBase(DemorgansLawIntention::cla
             let _ = !(!b && !true);
         }
     """)
+
+    fun `test keep parens if needed 1`() = doAvailableTest("""
+        fn main() {
+            !(b /*caret*/&& c) || a;
+        }
+    """, """
+        fn main() {
+            !b || !c || a;
+        }
+    """)
+
+    fun `test keep parens if needed 2`() = doAvailableTest("""
+        fn main() {
+            !(b /*caret*/&& c) && a;
+        }
+    """, """
+        fn main() {
+            (!b || !c) && a;
+        }
+    """)
+
+    fun `test keep parens if needed 3`() = doAvailableTest("""
+        fn main() {
+            !(b /*caret*/|| c) || a;
+        }
+    """, """
+        fn main() {
+            !b && !c || a;
+        }
+    """)
+
+    fun `test keep parens if needed 4`() = doAvailableTest("""
+        fn main() {
+            !(b /*caret*/|| c) && a;
+        }
+    """, """
+        fn main() {
+            !b && !c && a;
+        }
+    """)
 }