about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-08-09 07:05:58 +0200
committerGitHub <noreply@github.com>2022-08-09 07:05:58 +0200
commitd63d2bd67fb4741eeb8b82efaf5d2aa2ab8d60c7 (patch)
treee6bc757a582b3c924647f871264cedd0d543dc0c
parent65cc68b3fd164f351287ac88daf4302e9842cb37 (diff)
parent74e71da5472f92988bed09d3b79470901ef8632f (diff)
downloadrust-d63d2bd67fb4741eeb8b82efaf5d2aa2ab8d60c7.tar.gz
rust-d63d2bd67fb4741eeb8b82efaf5d2aa2ab8d60c7.zip
Rollup merge of #100238 - Bryysen:master, r=cjgillot
Further improve error message for E0081

Closes #97533
-rw-r--r--compiler/rustc_typeck/src/check/check.rs158
-rw-r--r--compiler/rustc_typeck/src/check/mod.rs1
-rw-r--r--src/test/ui/enum/enum-discrim-autosizing.rs4
-rw-r--r--src/test/ui/enum/enum-discrim-autosizing.stderr4
-rw-r--r--src/test/ui/error-codes/E0081.rs37
-rw-r--r--src/test/ui/error-codes/E0081.stderr52
-rw-r--r--src/test/ui/issues/issue-15524.rs16
-rw-r--r--src/test/ui/issues/issue-15524.stderr40
-rw-r--r--src/test/ui/tag-variant-disr-dup.rs12
-rw-r--r--src/test/ui/tag-variant-disr-dup.stderr14
10 files changed, 173 insertions, 165 deletions
diff --git a/compiler/rustc_typeck/src/check/check.rs b/compiler/rustc_typeck/src/check/check.rs
index 9c1fd9b30b4..c5fa8a5db03 100644
--- a/compiler/rustc_typeck/src/check/check.rs
+++ b/compiler/rustc_typeck/src/check/check.rs
@@ -32,7 +32,6 @@ use rustc_trait_selection::traits::error_reporting::InferCtxtExt as _;
 use rustc_trait_selection::traits::{self, ObligationCtxt};
 use rustc_ty_utils::representability::{self, Representability};
 
-use std::iter;
 use std::ops::ControlFlow;
 
 pub(super) fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Abi) {
@@ -1494,76 +1493,109 @@ fn check_enum<'tcx>(tcx: TyCtxt<'tcx>, vs: &'tcx [hir::Variant<'tcx>], def_id: L
         }
     }
 
-    let mut disr_vals: Vec<Discr<'tcx>> = Vec::with_capacity(vs.len());
-    // This tracks the previous variant span (in the loop) incase we need it for diagnostics
-    let mut prev_variant_span: Span = DUMMY_SP;
-    for ((_, discr), v) in iter::zip(def.discriminants(tcx), vs) {
-        // Check for duplicate discriminant values
-        if let Some(i) = disr_vals.iter().position(|&x| x.val == discr.val) {
-            let variant_did = def.variant(VariantIdx::new(i)).def_id;
-            let variant_i_hir_id = tcx.hir().local_def_id_to_hir_id(variant_did.expect_local());
-            let variant_i = tcx.hir().expect_variant(variant_i_hir_id);
-            let i_span = match variant_i.disr_expr {
-                Some(ref expr) => tcx.hir().span(expr.hir_id),
-                None => tcx.def_span(variant_did),
-            };
-            let span = match v.disr_expr {
-                Some(ref expr) => tcx.hir().span(expr.hir_id),
-                None => v.span,
-            };
-            let display_discr = format_discriminant_overflow(tcx, v, discr);
-            let display_discr_i = format_discriminant_overflow(tcx, variant_i, disr_vals[i]);
-            let no_disr = v.disr_expr.is_none();
-            let mut err = struct_span_err!(
-                tcx.sess,
-                sp,
-                E0081,
-                "discriminant value `{}` assigned more than once",
-                discr,
-            );
-
-            err.span_label(i_span, format!("first assignment of {display_discr_i}"));
-            err.span_label(span, format!("second assignment of {display_discr}"));
-
-            if no_disr {
-                err.span_label(
-                    prev_variant_span,
-                    format!(
-                        "assigned discriminant for `{}` was incremented from this discriminant",
-                        v.ident
-                    ),
-                );
-            }
-            err.emit();
-        }
-
-        disr_vals.push(discr);
-        prev_variant_span = v.span;
-    }
+    detect_discriminant_duplicate(tcx, def.discriminants(tcx).collect(), vs, sp);
 
     check_representable(tcx, sp, def_id);
     check_transparent(tcx, sp, def);
 }
 
-/// In the case that a discriminant is both a duplicate and an overflowing literal,
-/// we insert both the assigned discriminant and the literal it overflowed from into the formatted
-/// output. Otherwise we format the discriminant normally.
-fn format_discriminant_overflow<'tcx>(
+/// Part of enum check. Given the discriminants of an enum, errors if two or more discriminants are equal
+fn detect_discriminant_duplicate<'tcx>(
     tcx: TyCtxt<'tcx>,
-    variant: &hir::Variant<'_>,
-    dis: Discr<'tcx>,
-) -> String {
-    if let Some(expr) = &variant.disr_expr {
-        let body = &tcx.hir().body(expr.body).value;
-        if let hir::ExprKind::Lit(lit) = &body.kind
-            && let rustc_ast::LitKind::Int(lit_value, _int_kind) = &lit.node
-            && dis.val != *lit_value
-        {
-                    return format!("`{dis}` (overflowed from `{lit_value}`)");
+    mut discrs: Vec<(VariantIdx, Discr<'tcx>)>,
+    vs: &'tcx [hir::Variant<'tcx>],
+    self_span: Span,
+) {
+    // Helper closure to reduce duplicate code. This gets called everytime we detect a duplicate.
+    // Here `idx` refers to the order of which the discriminant appears, and its index in `vs`
+    let report = |dis: Discr<'tcx>,
+                  idx: usize,
+                  err: &mut DiagnosticBuilder<'_, ErrorGuaranteed>| {
+        let var = &vs[idx]; // HIR for the duplicate discriminant
+        let (span, display_discr) = match var.disr_expr {
+            Some(ref expr) => {
+                // In the case the discriminant is both a duplicate and overflowed, let the user know
+                if let hir::ExprKind::Lit(lit) = &tcx.hir().body(expr.body).value.kind
+                    && let rustc_ast::LitKind::Int(lit_value, _int_kind) = &lit.node
+                    && *lit_value != dis.val
+                {
+                    (tcx.hir().span(expr.hir_id), format!("`{dis}` (overflowed from `{lit_value}`)"))
+                // Otherwise, format the value as-is
+                } else {
+                    (tcx.hir().span(expr.hir_id), format!("`{dis}`"))
+                }
+            }
+            None => {
+                // At this point we know this discriminant is a duplicate, and was not explicitly
+                // assigned by the user. Here we iterate backwards to fetch the HIR for the last
+                // explictly assigned discriminant, and letting the user know that this was the
+                // increment startpoint, and how many steps from there leading to the duplicate
+                if let Some((n, hir::Variant { span, ident, .. })) =
+                    vs[..idx].iter().rev().enumerate().find(|v| v.1.disr_expr.is_some())
+                {
+                    let ve_ident = var.ident;
+                    let n = n + 1;
+                    let sp = if n > 1 { "variants" } else { "variant" };
+
+                    err.span_label(
+                        *span,
+                        format!("discriminant for `{ve_ident}` incremented from this startpoint (`{ident}` + {n} {sp} later => `{ve_ident}` = {dis})"),
+                    );
+                }
+
+                (vs[idx].span, format!("`{dis}`"))
+            }
+        };
+
+        err.span_label(span, format!("{display_discr} assigned here"));
+    };
+
+    // Here we loop through the discriminants, comparing each discriminant to another.
+    // When a duplicate is detected, we instatiate an error and point to both
+    // initial and duplicate value. The duplicate discriminant is then discarded by swapping
+    // it with the last element and decrementing the `vec.len` (which is why we have to evaluate
+    // `discrs.len()` anew every iteration, and why this could be tricky to do in a functional
+    // style as we are mutating `discrs` on the fly).
+    let mut i = 0;
+    while i < discrs.len() {
+        let hir_var_i_idx = discrs[i].0.index();
+        let mut error: Option<DiagnosticBuilder<'_, _>> = None;
+
+        let mut o = i + 1;
+        while o < discrs.len() {
+            let hir_var_o_idx = discrs[o].0.index();
+
+            if discrs[i].1.val == discrs[o].1.val {
+                let err = error.get_or_insert_with(|| {
+                    let mut ret = struct_span_err!(
+                        tcx.sess,
+                        self_span,
+                        E0081,
+                        "discriminant value `{}` assigned more than once",
+                        discrs[i].1,
+                    );
+
+                    report(discrs[i].1, hir_var_i_idx, &mut ret);
+
+                    ret
+                });
+
+                report(discrs[o].1, hir_var_o_idx, err);
+
+                // Safe to unwrap here, as we wouldn't reach this point if `discrs` was empty
+                discrs[o] = *discrs.last().unwrap();
+                discrs.pop();
+            } else {
+                o += 1;
+            }
         }
-    }
 
-    format!("`{dis}`")
+        if let Some(mut e) = error {
+            e.emit();
+        }
+
+        i += 1;
+    }
 }
 
 pub(super) fn check_type_params_are_used<'tcx>(
diff --git a/compiler/rustc_typeck/src/check/mod.rs b/compiler/rustc_typeck/src/check/mod.rs
index 17c2e4868aa..199277abbc4 100644
--- a/compiler/rustc_typeck/src/check/mod.rs
+++ b/compiler/rustc_typeck/src/check/mod.rs
@@ -112,7 +112,6 @@ use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::intravisit::Visitor;
 use rustc_hir::{HirIdMap, ImplicitSelfKind, Node};
 use rustc_index::bit_set::BitSet;
-use rustc_index::vec::Idx;
 use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_middle::ty::query::Providers;
 use rustc_middle::ty::subst::{InternalSubsts, Subst, SubstsRef};
diff --git a/src/test/ui/enum/enum-discrim-autosizing.rs b/src/test/ui/enum/enum-discrim-autosizing.rs
index 27fab1bb505..fc94d281c77 100644
--- a/src/test/ui/enum/enum-discrim-autosizing.rs
+++ b/src/test/ui/enum/enum-discrim-autosizing.rs
@@ -6,9 +6,9 @@
 enum Eu64 {
     //~^ ERROR discriminant value `0` assigned more than once
     Au64 = 0,
-    //~^NOTE first assignment of `0`
+    //~^NOTE `0` assigned here
     Bu64 = 0x8000_0000_0000_0000
-    //~^NOTE second assignment of `0` (overflowed from `9223372036854775808`)
+    //~^NOTE `0` (overflowed from `9223372036854775808`) assigned here
 }
 
 fn main() {}
diff --git a/src/test/ui/enum/enum-discrim-autosizing.stderr b/src/test/ui/enum/enum-discrim-autosizing.stderr
index eacea86d67e..be3d7c64e28 100644
--- a/src/test/ui/enum/enum-discrim-autosizing.stderr
+++ b/src/test/ui/enum/enum-discrim-autosizing.stderr
@@ -5,10 +5,10 @@ LL | enum Eu64 {
    | ^^^^^^^^^
 LL |
 LL |     Au64 = 0,
-   |            - first assignment of `0`
+   |            - `0` assigned here
 LL |
 LL |     Bu64 = 0x8000_0000_0000_0000
-   |            --------------------- second assignment of `0` (overflowed from `9223372036854775808`)
+   |            --------------------- `0` (overflowed from `9223372036854775808`) assigned here
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/error-codes/E0081.rs b/src/test/ui/error-codes/E0081.rs
index 5aa6a786339..f53fda864d6 100644
--- a/src/test/ui/error-codes/E0081.rs
+++ b/src/test/ui/error-codes/E0081.rs
@@ -1,9 +1,9 @@
 enum Enum {
     //~^ ERROR discriminant value `3` assigned more than once
     P = 3,
-    //~^ NOTE first assignment of `3`
+    //~^ NOTE `3` assigned here
     X = 3,
-    //~^ NOTE second assignment of `3`
+    //~^ NOTE `3` assigned here
     Y = 5
 }
 
@@ -11,20 +11,43 @@ enum Enum {
 enum EnumOverflowRepr {
     //~^ ERROR discriminant value `1` assigned more than once
     P = 257,
-    //~^ NOTE first assignment of `1` (overflowed from `257`)
+    //~^ NOTE `1` (overflowed from `257`) assigned here
     X = 513,
-    //~^ NOTE second assignment of `1` (overflowed from `513`)
+    //~^ NOTE `1` (overflowed from `513`) assigned here
 }
 
 #[repr(i8)]
 enum NegDisEnum {
     //~^ ERROR discriminant value `-1` assigned more than once
     First = -1,
-    //~^ NOTE first assignment of `-1`
+    //~^ NOTE `-1` assigned here
     Second = -2,
-    //~^ NOTE assigned discriminant for `Last` was incremented from this discriminant
+    //~^ NOTE discriminant for `Last` incremented from this startpoint (`Second` + 1 variant later => `Last` = -1)
     Last,
-    //~^ NOTE second assignment of `-1`
+    //~^ NOTE `-1` assigned here
+}
+
+enum MultipleDuplicates {
+    //~^ ERROR discriminant value `0` assigned more than once
+    //~^^ ERROR discriminant value `-2` assigned more than once
+    V0,
+    //~^ NOTE `0` assigned here
+    V1 = 0,
+    //~^ NOTE `0` assigned here
+    V2,
+    V3,
+    V4 = 0,
+    //~^ NOTE `0` assigned here
+    V5 = -2,
+    //~^ NOTE discriminant for `V7` incremented from this startpoint (`V5` + 2 variants later => `V7` = 0)
+    //~^^ NOTE `-2` assigned here
+    V6,
+    V7,
+    //~^ NOTE `0` assigned here
+    V8 = -3,
+    //~^ NOTE discriminant for `V9` incremented from this startpoint (`V8` + 1 variant later => `V9` = -2)
+    V9,
+    //~^ NOTE `-2` assigned here
 }
 
 fn main() {
diff --git a/src/test/ui/error-codes/E0081.stderr b/src/test/ui/error-codes/E0081.stderr
index ff6113646bb..64562fefc86 100644
--- a/src/test/ui/error-codes/E0081.stderr
+++ b/src/test/ui/error-codes/E0081.stderr
@@ -5,10 +5,10 @@ LL | enum Enum {
    | ^^^^^^^^^
 LL |
 LL |     P = 3,
-   |         - first assignment of `3`
+   |         - `3` assigned here
 LL |
 LL |     X = 3,
-   |         - second assignment of `3`
+   |         - `3` assigned here
 
 error[E0081]: discriminant value `1` assigned more than once
   --> $DIR/E0081.rs:11:1
@@ -17,10 +17,10 @@ LL | enum EnumOverflowRepr {
    | ^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL |     P = 257,
-   |         --- first assignment of `1` (overflowed from `257`)
+   |         --- `1` (overflowed from `257`) assigned here
 LL |
 LL |     X = 513,
-   |         --- second assignment of `1` (overflowed from `513`)
+   |         --- `1` (overflowed from `513`) assigned here
 
 error[E0081]: discriminant value `-1` assigned more than once
   --> $DIR/E0081.rs:20:1
@@ -29,14 +29,50 @@ LL | enum NegDisEnum {
    | ^^^^^^^^^^^^^^^
 LL |
 LL |     First = -1,
-   |             -- first assignment of `-1`
+   |             -- `-1` assigned here
 LL |
 LL |     Second = -2,
-   |     ----------- assigned discriminant for `Last` was incremented from this discriminant
+   |     ----------- discriminant for `Last` incremented from this startpoint (`Second` + 1 variant later => `Last` = -1)
 LL |
 LL |     Last,
-   |     ---- second assignment of `-1`
+   |     ---- `-1` assigned here
 
-error: aborting due to 3 previous errors
+error[E0081]: discriminant value `0` assigned more than once
+  --> $DIR/E0081.rs:30:1
+   |
+LL | enum MultipleDuplicates {
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL |     V0,
+   |     -- `0` assigned here
+LL |
+LL |     V1 = 0,
+   |          - `0` assigned here
+...
+LL |     V4 = 0,
+   |          - `0` assigned here
+LL |
+LL |     V5 = -2,
+   |     ------- discriminant for `V7` incremented from this startpoint (`V5` + 2 variants later => `V7` = 0)
+...
+LL |     V7,
+   |     -- `0` assigned here
+
+error[E0081]: discriminant value `-2` assigned more than once
+  --> $DIR/E0081.rs:30:1
+   |
+LL | enum MultipleDuplicates {
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+...
+LL |     V5 = -2,
+   |          -- `-2` assigned here
+...
+LL |     V8 = -3,
+   |     ------- discriminant for `V9` incremented from this startpoint (`V8` + 1 variant later => `V9` = -2)
+LL |
+LL |     V9,
+   |     -- `-2` assigned here
+
+error: aborting due to 5 previous errors
 
 For more information about this error, try `rustc --explain E0081`.
diff --git a/src/test/ui/issues/issue-15524.rs b/src/test/ui/issues/issue-15524.rs
deleted file mode 100644
index 565db2d0fca..00000000000
--- a/src/test/ui/issues/issue-15524.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-const N: isize = 1;
-
-enum Foo {
-    //~^ ERROR discriminant value `1` assigned more than once
-    //~| ERROR discriminant value `1` assigned more than once
-    //~| ERROR discriminant value `1` assigned more than once
-    A = 1,
-    B = 1,
-    C = 0,
-    D,
-
-    E = N,
-
-}
-
-fn main() {}
diff --git a/src/test/ui/issues/issue-15524.stderr b/src/test/ui/issues/issue-15524.stderr
deleted file mode 100644
index 1195e0a346d..00000000000
--- a/src/test/ui/issues/issue-15524.stderr
+++ /dev/null
@@ -1,40 +0,0 @@
-error[E0081]: discriminant value `1` assigned more than once
-  --> $DIR/issue-15524.rs:3:1
-   |
-LL | enum Foo {
-   | ^^^^^^^^
-...
-LL |     A = 1,
-   |         - first assignment of `1`
-LL |     B = 1,
-   |         - second assignment of `1`
-
-error[E0081]: discriminant value `1` assigned more than once
-  --> $DIR/issue-15524.rs:3:1
-   |
-LL | enum Foo {
-   | ^^^^^^^^
-...
-LL |     A = 1,
-   |         - first assignment of `1`
-LL |     B = 1,
-LL |     C = 0,
-   |     ----- assigned discriminant for `D` was incremented from this discriminant
-LL |     D,
-   |     - second assignment of `1`
-
-error[E0081]: discriminant value `1` assigned more than once
-  --> $DIR/issue-15524.rs:3:1
-   |
-LL | enum Foo {
-   | ^^^^^^^^
-...
-LL |     A = 1,
-   |         - first assignment of `1`
-...
-LL |     E = N,
-   |         - second assignment of `1`
-
-error: aborting due to 3 previous errors
-
-For more information about this error, try `rustc --explain E0081`.
diff --git a/src/test/ui/tag-variant-disr-dup.rs b/src/test/ui/tag-variant-disr-dup.rs
deleted file mode 100644
index e497f993da2..00000000000
--- a/src/test/ui/tag-variant-disr-dup.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Black and White have the same discriminator value ...
-
-enum Color {
-    //~^ ERROR discriminant value `0` assigned more than once
-    Red = 0xff0000,
-    Green = 0x00ff00,
-    Blue = 0x0000ff,
-    Black = 0x000000,
-    White = 0x000000,
-}
-
-fn main() { }
diff --git a/src/test/ui/tag-variant-disr-dup.stderr b/src/test/ui/tag-variant-disr-dup.stderr
deleted file mode 100644
index 6b1ba43d2ba..00000000000
--- a/src/test/ui/tag-variant-disr-dup.stderr
+++ /dev/null
@@ -1,14 +0,0 @@
-error[E0081]: discriminant value `0` assigned more than once
-  --> $DIR/tag-variant-disr-dup.rs:3:1
-   |
-LL | enum Color {
-   | ^^^^^^^^^^
-...
-LL |     Black = 0x000000,
-   |             -------- first assignment of `0`
-LL |     White = 0x000000,
-   |             -------- second assignment of `0`
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0081`.