about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNadrieril <nadrieril+git@gmail.com>2023-10-12 19:47:33 +0200
committerNadrieril <nadrieril+git@gmail.com>2023-10-27 19:56:12 +0200
commit6f35ae6f9bc32f591262c6eee94452e3eb2918d5 (patch)
tree9b791604950b1908a581f8a85930cf10a3de63ea
parent9bc4c378ab3bbdd330ccd1268fae478a0f0905f2 (diff)
downloadrust-6f35ae6f9bc32f591262c6eee94452e3eb2918d5.tar.gz
rust-6f35ae6f9bc32f591262c6eee94452e3eb2918d5.zip
Propagate half-open ranges through exhaustiveness checking
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs253
-rw-r--r--compiler/rustc_mir_build/src/thir/pattern/usefulness.rs7
2 files changed, 158 insertions, 102 deletions
diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
index 249273c3595..00a8bd68773 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs
@@ -63,6 +63,7 @@ use rustc_span::{Span, DUMMY_SP};
 use rustc_target::abi::{FieldIdx, Integer, VariantIdx, FIRST_VARIANT};
 
 use self::Constructor::*;
+use self::MaybeInfiniteInt::*;
 use self::SliceKind::*;
 
 use super::usefulness::{MatchCheckCtxt, PatCtxt};
@@ -91,20 +92,99 @@ enum Presence {
     Seen,
 }
 
+/// A possibly infinite integer. Values are encoded such that the ordering on `u128` matches the
+/// natural order on the original type. For example, `-128i8` is encoded as `0` and `127i8` as
+/// `255`. See `signed_bias` for details.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub(crate) enum MaybeInfiniteInt {
+    NegInfinity,
+    /// Encoded value. DO NOT CONSTRUCT BY HAND; use `new_finite`.
+    Finite(u128),
+    /// The integer after `u128::MAX`. Used when we switch to exclusive ranges in `IntRange::split`.
+    JustAfterMax,
+    PosInfinity,
+}
+
+impl MaybeInfiniteInt {
+    // The return value of `signed_bias` should be XORed with a value to encode/decode it.
+    fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 {
+        match *ty.kind() {
+            ty::Int(ity) => {
+                let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128;
+                1u128 << (bits - 1)
+            }
+            _ => 0,
+        }
+    }
+
+    fn new_finite(tcx: TyCtxt<'_>, ty: Ty<'_>, bits: u128) -> Self {
+        let bias = Self::signed_bias(tcx, ty);
+        // Perform a shift if the underlying types are signed, which makes the interval arithmetic
+        // type-independent.
+        let x = bits ^ bias;
+        Finite(x)
+    }
+    fn from_pat_range_bdy<'tcx>(
+        bdy: PatRangeBoundary<'tcx>,
+        ty: Ty<'tcx>,
+        tcx: TyCtxt<'tcx>,
+        param_env: ty::ParamEnv<'tcx>,
+    ) -> Self {
+        match bdy {
+            PatRangeBoundary::NegInfinity => NegInfinity,
+            PatRangeBoundary::Finite(value) => {
+                let bits = value.eval_bits(tcx, param_env);
+                Self::new_finite(tcx, ty, bits)
+            }
+            PatRangeBoundary::PosInfinity => PosInfinity,
+        }
+    }
+    fn to_pat_range_bdy<'tcx>(self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> PatRangeBoundary<'tcx> {
+        match self {
+            NegInfinity => PatRangeBoundary::NegInfinity,
+            Finite(x) => {
+                let bias = Self::signed_bias(tcx, ty);
+                let bits = x ^ bias;
+                let env = ty::ParamEnv::empty().and(ty);
+                let value = mir::Const::from_bits(tcx, bits, env);
+                PatRangeBoundary::Finite(value)
+            }
+            JustAfterMax | PosInfinity => PatRangeBoundary::PosInfinity,
+        }
+    }
+
+    fn minus_one(self) -> Self {
+        match self {
+            Finite(n) => match n.checked_sub(1) {
+                Some(m) => Finite(m),
+                None => NegInfinity,
+            },
+            JustAfterMax => Finite(u128::MAX),
+            x => x,
+        }
+    }
+    fn plus_one(self) -> Self {
+        match self {
+            Finite(n) => match n.checked_add(1) {
+                Some(m) => Finite(m),
+                None => JustAfterMax,
+            },
+            x => x,
+        }
+    }
+}
+
 /// An inclusive interval, used for precise integer exhaustiveness checking.
-/// `IntRange`s always store a contiguous range. This means that values are
-/// encoded such that `0` encodes the minimum value for the integer,
-/// regardless of the signedness.
-/// For example, the pattern `-128..=127i8` is encoded as `0..=255`.
-/// This makes comparisons and arithmetic on interval endpoints much more
-/// straightforward. See `signed_bias` for details.
+/// `IntRange`s always store a contiguous range.
 ///
 /// `IntRange` is never used to encode an empty range or a "range" that wraps
 /// around the (offset) space: i.e., `range.lo <= range.hi`.
+///
+/// The range can have open ends.
 #[derive(Clone, Copy, PartialEq, Eq)]
 pub(crate) struct IntRange {
-    pub(crate) lo: u128,
-    pub(crate) hi: u128,
+    pub(crate) lo: MaybeInfiniteInt, // Must not be `PosInfinity`.
+    pub(crate) hi: MaybeInfiniteInt, // Must not be `NegInfinity`.
 }
 
 impl IntRange {
@@ -113,51 +193,31 @@ impl IntRange {
         matches!(ty.kind(), ty::Char | ty::Int(_) | ty::Uint(_))
     }
 
+    /// Best effort; will not know that e.g. `255u8..` is a singleton.
     pub(super) fn is_singleton(&self) -> bool {
+        // Since `lo` and `hi` can't be the same `Infinity`, this correctly only detects a
+        // `Finite(x)` singleton.
         self.lo == self.hi
     }
 
     #[inline]
     fn from_bits<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, bits: u128) -> IntRange {
-        let bias = IntRange::signed_bias(tcx, ty);
-        // Perform a shift if the underlying types are signed, which makes the interval arithmetic
-        // type-independent.
-        let val = bits ^ bias;
-        IntRange { lo: val, hi: val }
+        let x = MaybeInfiniteInt::new_finite(tcx, ty, bits);
+        IntRange { lo: x, hi: x }
     }
 
     #[inline]
-    fn from_range<'tcx>(
-        tcx: TyCtxt<'tcx>,
-        lo: u128,
-        hi: u128,
-        ty: Ty<'tcx>,
-        end: RangeEnd,
-    ) -> IntRange {
-        // Perform a shift if the underlying types are signed, which makes the interval arithmetic
-        // type-independent.
-        let bias = IntRange::signed_bias(tcx, ty);
-        let (lo, hi) = (lo ^ bias, hi ^ bias);
-        let offset = (end == RangeEnd::Excluded) as u128;
-        let hi = hi - offset;
+    fn from_range(lo: MaybeInfiniteInt, mut hi: MaybeInfiniteInt, end: RangeEnd) -> IntRange {
+        if end == RangeEnd::Excluded {
+            hi = hi.minus_one();
+        }
         if lo > hi {
             // This should have been caught earlier by E0030.
-            bug!("malformed range pattern: {lo}..={hi}");
+            bug!("malformed range pattern: {lo:?}..={hi:?}");
         }
         IntRange { lo, hi }
     }
 
-    // The return value of `signed_bias` should be XORed with an endpoint to encode/decode it.
-    fn signed_bias(tcx: TyCtxt<'_>, ty: Ty<'_>) -> u128 {
-        match *ty.kind() {
-            ty::Int(ity) => {
-                let bits = Integer::from_int_ty(&tcx, ity).size().bits() as u128;
-                1u128 << (bits - 1)
-            }
-            _ => 0,
-        }
-    }
-
     fn is_subrange(&self, other: &Self) -> bool {
         other.lo <= self.lo && self.hi <= other.hi
     }
@@ -201,29 +261,16 @@ impl IntRange {
         &self,
         column_ranges: impl Iterator<Item = IntRange>,
     ) -> impl Iterator<Item = (Presence, IntRange)> {
-        /// Represents a boundary between 2 integers. Because the intervals spanning boundaries must be
-        /// able to cover every integer, we need to be able to represent 2^128 + 1 such boundaries.
-        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-        enum IntBoundary {
-            JustBefore(u128),
-            AfterMax,
-        }
-
-        fn unpack_intrange(range: IntRange) -> [IntBoundary; 2] {
-            use IntBoundary::*;
-            let lo = JustBefore(range.lo);
-            let hi = match range.hi.checked_add(1) {
-                Some(m) => JustBefore(m),
-                None => AfterMax,
-            };
-            [lo, hi]
+        // Make the range into an exclusive range.
+        fn unpack_intrange(range: IntRange) -> [MaybeInfiniteInt; 2] {
+            [range.lo, range.hi.plus_one()]
         }
 
         // The boundaries of ranges in `column_ranges` intersected with `self`.
         // We do parenthesis matching for input ranges. A boundary counts as +1 if it starts
         // a range and -1 if it ends it. When the count is > 0 between two boundaries, we
         // are within an input range.
-        let mut boundaries: Vec<(IntBoundary, isize)> = column_ranges
+        let mut boundaries: Vec<(MaybeInfiniteInt, isize)> = column_ranges
             .filter_map(|r| self.intersection(&r))
             .map(unpack_intrange)
             .flat_map(|[lo, hi]| [(lo, 1), (hi, -1)])
@@ -233,7 +280,7 @@ impl IntRange {
         // the accumulated count between distinct boundary values.
         boundaries.sort_unstable();
 
-        let [self_start, self_end] = unpack_intrange(self.clone());
+        let [self_start, self_end] = unpack_intrange(*self);
         // Accumulate parenthesis counts.
         let mut paren_counter = 0isize;
         // Gather pairs of adjacent boundaries.
@@ -255,36 +302,26 @@ impl IntRange {
             .filter(|&(prev_bdy, _, bdy)| prev_bdy != bdy)
             // Convert back to ranges.
             .map(move |(prev_bdy, paren_count, bdy)| {
-                use IntBoundary::*;
                 use Presence::*;
                 let presence = if paren_count > 0 { Seen } else { Unseen };
-                let (lo, hi) = match (prev_bdy, bdy) {
-                    (JustBefore(n), JustBefore(m)) if n < m => (n, m - 1),
-                    (JustBefore(n), AfterMax) => (n, u128::MAX),
-                    _ => unreachable!(), // Ruled out by the sorting and filtering we did
-                };
-                (presence, IntRange { lo, hi })
+                // Turn back into an inclusive range.
+                let range = IntRange::from_range(prev_bdy, bdy, RangeEnd::Excluded);
+                (presence, range)
             })
     }
 
     /// Only used for displaying the range.
-    pub(super) fn to_pat<'tcx>(&self, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Pat<'tcx> {
-        let bias = IntRange::signed_bias(tcx, ty);
-        let (lo_bits, hi_bits) = (self.lo ^ bias, self.hi ^ bias);
-
-        let env = ty::ParamEnv::empty().and(ty);
-        let lo_const = mir::Const::from_bits(tcx, lo_bits, env);
-        let hi_const = mir::Const::from_bits(tcx, hi_bits, env);
-
-        let kind = if lo_bits == hi_bits {
-            PatKind::Constant { value: lo_const }
+    pub(super) fn to_pat<'tcx>(&self, ty: Ty<'tcx>, tcx: TyCtxt<'tcx>) -> Pat<'tcx> {
+        let lo = self.lo.to_pat_range_bdy(ty, tcx);
+        let hi = self.hi.to_pat_range_bdy(ty, tcx);
+
+        let kind = if self.is_singleton() {
+            let value = lo.as_finite().unwrap();
+            PatKind::Constant { value }
+        } else if matches!((self.lo, self.hi), (NegInfinity, PosInfinity)) {
+            PatKind::Wild
         } else {
-            PatKind::Range(Box::new(PatRange {
-                lo: PatRangeBoundary::Finite(lo_const),
-                hi: PatRangeBoundary::Finite(hi_const),
-                end: RangeEnd::Included,
-                ty,
-            }))
+            PatKind::Range(Box::new(PatRange { lo, hi, end: RangeEnd::Included, ty }))
         };
 
         Pat { ty, span: DUMMY_SP, kind }
@@ -295,10 +332,14 @@ impl IntRange {
 /// first.
 impl fmt::Debug for IntRange {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let (lo, hi) = (self.lo, self.hi);
-        write!(f, "{lo}")?;
+        if let Finite(lo) = self.lo {
+            write!(f, "{lo}")?;
+        }
         write!(f, "{}", RangeEnd::Included)?;
-        write!(f, "{hi}")
+        if let Finite(hi) = self.hi {
+            write!(f, "{hi}")?;
+        }
+        Ok(())
     }
 }
 
@@ -840,8 +881,13 @@ pub(super) struct SplitConstructorSet<'tcx> {
 impl ConstructorSet {
     #[instrument(level = "debug", skip(cx), ret)]
     pub(super) fn for_ty<'p, 'tcx>(cx: &MatchCheckCtxt<'p, 'tcx>, ty: Ty<'tcx>) -> Self {
-        let make_range =
-            |start, end| IntRange::from_range(cx.tcx, start, end, ty, RangeEnd::Included);
+        let make_range = |start, end| {
+            IntRange::from_range(
+                MaybeInfiniteInt::new_finite(cx.tcx, ty, start),
+                MaybeInfiniteInt::new_finite(cx.tcx, ty, end),
+                RangeEnd::Included,
+            )
+        };
         // This determines the set of all possible constructors for the type `ty`. For numbers,
         // arrays and slices we use ranges and variable-length slices when appropriate.
         //
@@ -1419,24 +1465,33 @@ impl<'p, 'tcx> DeconstructedPat<'p, 'tcx> {
                 }
             }
             PatKind::Range(box PatRange { lo, hi, end, .. }) => {
-                use rustc_apfloat::Float;
                 let ty = pat.ty;
-                // FIXME: handle half-open ranges
-                let lo = lo.eval_bits(ty, cx.tcx, cx.param_env);
-                let hi = hi.eval_bits(ty, cx.tcx, cx.param_env);
                 ctor = match ty.kind() {
                     ty::Char | ty::Int(_) | ty::Uint(_) => {
-                        IntRange(IntRange::from_range(cx.tcx, lo, hi, ty, *end))
-                    }
-                    ty::Float(ty::FloatTy::F32) => {
-                        let lo = rustc_apfloat::ieee::Single::from_bits(lo);
-                        let hi = rustc_apfloat::ieee::Single::from_bits(hi);
-                        F32Range(lo, hi, *end)
+                        let lo =
+                            MaybeInfiniteInt::from_pat_range_bdy(*lo, ty, cx.tcx, cx.param_env);
+                        let hi =
+                            MaybeInfiniteInt::from_pat_range_bdy(*hi, ty, cx.tcx, cx.param_env);
+                        IntRange(IntRange::from_range(lo, hi, *end))
                     }
-                    ty::Float(ty::FloatTy::F64) => {
-                        let lo = rustc_apfloat::ieee::Double::from_bits(lo);
-                        let hi = rustc_apfloat::ieee::Double::from_bits(hi);
-                        F64Range(lo, hi, *end)
+                    ty::Float(fty) => {
+                        use rustc_apfloat::Float;
+                        let lo = lo.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
+                        let hi = hi.as_finite().map(|c| c.eval_bits(cx.tcx, cx.param_env));
+                        match fty {
+                            ty::FloatTy::F32 => {
+                                use rustc_apfloat::ieee::Single;
+                                let lo = lo.map(Single::from_bits).unwrap_or(-Single::INFINITY);
+                                let hi = hi.map(Single::from_bits).unwrap_or(Single::INFINITY);
+                                F32Range(lo, hi, *end)
+                            }
+                            ty::FloatTy::F64 => {
+                                use rustc_apfloat::ieee::Double;
+                                let lo = lo.map(Double::from_bits).unwrap_or(-Double::INFINITY);
+                                let hi = hi.map(Double::from_bits).unwrap_or(Double::INFINITY);
+                                F64Range(lo, hi, *end)
+                            }
+                        }
                     }
                     _ => bug!("invalid type for range pattern: {}", ty),
                 };
@@ -1706,7 +1761,7 @@ impl<'tcx> WitnessPat<'tcx> {
         let mut subpatterns = self.iter_fields().map(|p| Box::new(p.to_pat(cx)));
         let kind = match &self.ctor {
             Bool(b) => PatKind::Constant { value: mir::Const::from_bool(cx.tcx, *b) },
-            IntRange(range) => return range.to_pat(cx.tcx, self.ty),
+            IntRange(range) => return range.to_pat(self.ty, cx.tcx),
             Single | Variant(_) => match self.ty.kind() {
                 ty::Tuple(..) => PatKind::Leaf {
                     subpatterns: subpatterns
diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
index 4b314535c89..3a210f2587e 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
@@ -308,7 +308,8 @@
 use self::ArmType::*;
 use self::Usefulness::*;
 use super::deconstruct_pat::{
-    Constructor, ConstructorSet, DeconstructedPat, IntRange, SplitConstructorSet, WitnessPat,
+    Constructor, ConstructorSet, DeconstructedPat, IntRange, MaybeInfiniteInt, SplitConstructorSet,
+    WitnessPat,
 };
 use crate::errors::{NonExhaustiveOmittedPattern, Overlap, OverlappingRangeEndpoints, Uncovered};
 
@@ -1013,7 +1014,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
 
     if IntRange::is_integral(ty) {
         let emit_lint = |overlap: &IntRange, this_span: Span, overlapped_spans: &[Span]| {
-            let overlap_as_pat = overlap.to_pat(cx.tcx, ty);
+            let overlap_as_pat = overlap.to_pat(ty, cx.tcx);
             let overlaps: Vec<_> = overlapped_spans
                 .iter()
                 .copied()
@@ -1031,7 +1032,7 @@ fn lint_overlapping_range_endpoints<'p, 'tcx>(
         let split_int_ranges = set.present.iter().filter_map(|c| c.as_int_range());
         for overlap_range in split_int_ranges.clone() {
             if overlap_range.is_singleton() {
-                let overlap: u128 = overlap_range.lo;
+                let overlap: MaybeInfiniteInt = overlap_range.lo;
                 // Ranges that look like `lo..=overlap`.
                 let mut prefixes: SmallVec<[_; 1]> = Default::default();
                 // Ranges that look like `overlap..=hi`.