Repository: intellij-rust/intellij-rust
Issue: False positive for E0081 (duplicated enum discriminants)

<!--
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.2.101.2129-191
* **Rust toolchain version:** 1.36.0 (a53f9df32 2019-07-03) x86_64-apple-darwin
* **IDE name and version:** IC-191.7479.19
* **Operating system:** macOS 10.14.5

## Problem description
When an enum variant has a discriminant that is not a literal, intellij-rust always thinks that the discriminant is 0, and thus may warn that a discriminant is used twice ([E0081](https://doc.rust-lang.org/error-index.html#E0081)).

## Steps to reproduce
```rust
#[repr(u32)]
enum Dx {
    LEFT = std::u32::MAX - 1,
    MIDDLE = 0,
    RIGHT = 1,
}
```      
[playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=590bdd397528e559b22c0e1559ce70b1)

Changed files:
src/main/kotlin/org/rust/lang/core/psi/ext/RsEnumItem.kt, src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt, src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt, src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.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 bd7de55db..d51711806 100644
--- a/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
+++ b/src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
@@ -34,6 +34,8 @@ import org.rust.lang.core.types.type
 import org.rust.lang.utils.RsDiagnostic
 import org.rust.lang.utils.RsErrorCode
 import org.rust.lang.utils.addToHolder
+import org.rust.lang.utils.evaluation.ExprValue
+import org.rust.lang.utils.evaluation.RsConstExprEvaluator
 
 class RsErrorAnnotator : RsAnnotatorBase(), HighlightRangeExtension {
     override fun isForceHighlightParents(file: PsiFile): Boolean = file is RsFile
@@ -119,10 +121,16 @@ class RsErrorAnnotator : RsAnnotatorBase(), HighlightRangeExtension {
         data class VariantInfo(val variant: RsEnumVariant, val alreadyReported: Boolean)
 
         var discrCounter = 0L
+        val reprType = (o.parent as? RsEnumItem)?.reprType ?: return
         val indexToVariantMap = hashMapOf<Long, VariantInfo>()
         for (variant in o.enumVariantList) {
-            val literal = variant.variantDiscriminant?.expr as? RsLitExpr
-            val int = literal?.integerValue
+            val expr = variant.variantDiscriminant?.expr
+            val int = if (expr != null) {
+                val result = RsConstExprEvaluator.evaluate(expr, reprType) as? ExprValue.Integer
+                result?.value ?: return
+            } else {
+                null
+            }
             val idx = int ?: discrCounter
             discrCounter = idx + 1
 
diff --git a/src/main/kotlin/org/rust/lang/core/psi/ext/RsEnumItem.kt b/src/main/kotlin/org/rust/lang/core/psi/ext/RsEnumItem.kt
index d1ce9deab..68eeec13f 100644
--- a/src/main/kotlin/org/rust/lang/core/psi/ext/RsEnumItem.kt
+++ b/src/main/kotlin/org/rust/lang/core/psi/ext/RsEnumItem.kt
@@ -18,6 +18,7 @@ import org.rust.lang.core.resolve.knownItems
 import org.rust.lang.core.stubs.RsEnumItemStub
 import org.rust.lang.core.types.RsPsiTypeImplUtil
 import org.rust.lang.core.types.ty.Ty
+import org.rust.lang.core.types.ty.TyInteger
 import javax.swing.Icon
 
 
@@ -48,3 +49,13 @@ val RsEnumItem.isStdOptionOrResult: Boolean get() {
 
 val RsEnumItem.variants: List<RsEnumVariant>
     get() = enumBody?.enumVariantList.orEmpty()
+
+// A repr attribute like #[repr(u16)] changes the discriminant type of an enum
+// https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri
+val RsEnumItem.reprType: TyInteger
+    get() = queryAttributes
+        .reprAttributes
+        .flatMap { it.metaItemArgs?.metaItemList?.asSequence() ?: emptySequence() }
+        .mapNotNull { it.name?.let { TyInteger.fromName(it) } }
+        .lastOrNull()
+        ?: TyInteger.ISize
diff --git a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
index 3f9cff176..0b301b90c 100644
--- a/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
+++ b/src/main/kotlin/org/rust/lang/core/types/infer/TypeInference.kt
@@ -168,16 +168,8 @@ class RsInferenceContext(
                 is RsConstant -> element.typeReference?.type to element.expr
                 is RsArrayType -> TyInteger.USize to element.expr
                 is RsVariantDiscriminant -> {
-                    // A repr attribute like #[repr(u16)] changes the discriminant type of an enum
-                    // https://doc.rust-lang.org/nomicon/other-reprs.html#repru-repri
                     val enum = element.ancestorStrict<RsEnumItem>()
-                    val reprType = enum?.queryAttributes?.reprAttributes
-                        ?.flatMap { it.metaItemArgs?.metaItemList?.asSequence() ?: emptySequence() }
-                        ?.mapNotNull { it.name?.let { TyInteger.fromName(it) } }
-                        ?.lastOrNull()
-                        ?: TyInteger.ISize
-
-                    reprType to element.expr
+                    enum?.reprType to element.expr
                 }
                 is RsExpressionCodeFragment -> {
                     element.context?.inference?.let {
diff --git a/src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt b/src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt
index 8a0b42a23..2b22682e0 100644
--- a/src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt
+++ b/src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt
@@ -546,9 +546,9 @@ sealed class RsDiagnostic(
     }
 
     class DuplicateEnumDiscriminant(
-        element: PsiElement,
+        variant: RsEnumVariant,
         private val id: Long
-    ) : RsDiagnostic(element) {
+    ) : RsDiagnostic(variant.variantDiscriminant?.expr ?: variant) {
         override fun prepare(): PreparedAnnotation = PreparedAnnotation(
             ERROR,
             E0081,
diff --git a/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt b/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
index f9e005e61..2b877c9a2 100644
--- a/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
+++ b/src/test/kotlin/org/rust/ide/annotator/RsErrorAnnotatorTest.kt
@@ -1693,24 +1693,24 @@ class RsErrorAnnotatorTest : RsAnnotatorTestBase(RsErrorAnnotator::class.java) {
     fun `test duplicate enum discriminant #1 E0081`() = checkErrors("""
         enum Bad {
             <error descr="Discriminant value `0` already exists [E0081]">X</error>,
-            <error descr="Discriminant value `0` already exists [E0081]">Y = 0</error>,
+            Y = <error descr="Discriminant value `0` already exists [E0081]">0</error>,
         }
     """)
 
     fun `test duplicate enum discriminant #2 E0081`() = checkErrors("""
         enum Bad {
-            <error descr="Discriminant value `3` already exists [E0081]">P = 3</error>,
+            P = <error descr="Discriminant value `3` already exists [E0081]">3</error>,
             Y = 0,
-            <error descr="Discriminant value `3` already exists [E0081]">X = 3</error>,
+            X = <error descr="Discriminant value `3` already exists [E0081]">3</error>,
         }
     """)
 
     fun `test duplicate enum discriminant #3 E0081`() = checkErrors("""
         enum Bad {
-            <error descr="Discriminant value `0` already exists [E0081]">X = 0</error>,
-            <error descr="Discriminant value `0` already exists [E0081]">Y = 0</error>,
-            <error descr="Discriminant value `0` already exists [E0081]">Z = 0</error>,
-            <error descr="Discriminant value `0` already exists [E0081]">W = 0</error>,
+            X = <error descr="Discriminant value `0` already exists [E0081]">0</error>,
+            Y = <error descr="Discriminant value `0` already exists [E0081]">0</error>,
+            Z = <error descr="Discriminant value `0` already exists [E0081]">0</error>,
+            W = <error descr="Discriminant value `0` already exists [E0081]">0</error>,
         }
     """)
 
@@ -1747,6 +1747,37 @@ class RsErrorAnnotatorTest : RsAnnotatorTestBase(RsErrorAnnotator::class.java) {
         }
     """)
 
+    fun `test duplicate enum discriminant const expr E0081`() = checkErrors("""
+                enum Bad {
+            X = <error descr="Discriminant value `3` already exists [E0081]">1 + 5 - 3</error>,
+            Y = <error descr="Discriminant value `3` already exists [E0081]">1 + 2</error>
+        }
+    """)
+
+    fun `test duplicate enum discriminant non-const expr E0081`() = checkErrors("""
+        fn foo() -> isize { 0 }
+        enum Good {
+            X = foo(),
+            Y = 0
+        }
+    """)
+
+    fun `test duplicate enum discriminant repr type valid range E0081`() = checkErrors("""
+        #[repr(i8)]
+        enum Bad {
+            X = <error descr="Discriminant value `-1` already exists [E0081]">-1</error>,
+            Y = <error descr="Discriminant value `-1` already exists [E0081]">-1</error>
+        }
+    """)
+
+    fun `test duplicate enum discriminant repr type invalid range E0081`() = checkErrors("""
+        #[repr(u8)]
+        enum Good {
+            X = -1,
+            Y = -1
+        }
+    """)
+
 
     fun `test E0040`() = checkErrors("""
         struct X;