Empty range pattern check is superfluous and inconvenient

The following currently fails to compile with the highly infuriating E0579:

match 1_u8 {
    0..0 => "intentionally dead arm",
    _ => "not so dead",
}

The use case is as follows:

macro_rules! match_range {
    ($e:expr, $ty:ty, {$($tt:tt)*}) => {
        match $e.get() {
            0..<$ty as $crate::int::RangeNewtype>::LOW
            | <$ty as $crate::int::RangeNewtype>::HIGH.. => unsafe { $crate::int::unreachable_unchecked() },
            $($tt)*
        }
    };
}

Here, $ty is a newtype wrapper around some integer type with a restricted range of possible values as generated by another macro that outputs an implementation of the (unsafe) RangeNewtype trait in addition to the type and its other methods. match_range!, then, is there to be a convenient, safe and zero-cost way to satisfy the exhaustiveness checker when matching the possible values of such a type.

The role of this problem in the greater range-restricting integer newtype story is marginal, as a properly compiler-visible and nicheopt-friendly way of doing this could just integrate with the exhaustiveness checker to remove the need for this macro entirely, but in the meantime, what could just as easily be a run-of-the-mill compiler warning is an unnecessary and obstructive hard error, as illustrated by the lack of convincing reasoning in favor of the existence of the error in the documentation of E0579.

3 Likes

You could give it some pre-processing

trait RangeNewtypeMatchBounds: RangeNewtype {
    const _MATCH_LOWER_BOUND: u8 = if Self::LOW > 0 { 0 } else { Self::HIGH };
    const _MATCH_UPPER_BOUND: u8 = if Self::LOW > 0 {
        Self::LOW - 1
    } else {
        Self::HIGH
    };
}
impl<T: RangeNewtype + ?Sized> RangeNewtypeMatchBounds for T {}

and then use

    <$ty as $crate::RangeNewtypeMatchBounds>::_MATCH_LOWER_BOUND
            ..=<$ty as $crate::RangeNewtypeMatchBounds>::_MATCH_UPPER_BOUND
    | <$ty as $crate::RangeNewtype>::HIGH.. => unsafe { $crate::unreachable_unchecked() },
    $($tt)*

I’m with @kotauskas on this one, 5..5 should be an error-by-default lint rather than a hard error, especially since that’s a valid range value you can form. (5..4 can continue being a hard error.)

8 Likes

What is a..b where a == 5 and b == 4 (at runtime)? The docs just say it is "empty" if a >= b, not a runtime error. So why compiler-error for something that has well-defined runtime behavior?

We’re talking about patterns, not expressions, and range patterns match integers, not other ranges. So while 5..5 and 5..4 both won’t match any actual values, the latter is much more likely to be a mistake, and so allowing the former while still diagnosing the latter is desirable. But sure, I guess it could be a second error-by-default lint.

4 Likes