Search code examples
swiftcastingswitch-statementrangedowncast

Swift range bug with switch-statement


Hello guys I'm new here and right now I'm learning Swift by coding some fancy algorithms, which comes to my mind while reading Apples Swift book.

I was trying to compress (automatically downcast) any IntegerType value. Here is a little snippet of my Code which almost works fine except for one case:

switch signedValue
{
case Int64(Int8.min)...Int64(Int8.max):          compressedValue = Int8(signedValue)
case (Int64(Int8.max) + 1)...Int64(UInt8.max):   compressedValue = UInt8(signedValue)
case Int64(Int16.min)...Int64(Int16.max):        compressedValue = Int16(signedValue)
case (Int64(Int16.max) + 1)...Int64(UInt16.max): compressedValue = UInt16(signedValue)
case Int64(Int32.min)...Int64(Int32.max):        compressedValue = Int32(signedValue)
case (Int64(Int32.max) + 1)...Int64(UInt32.max): compressedValue = UInt32(signedValue)
case Int64(Int.min)...Int64(Int.max):            compressedValue = Int(signedValue) // range bug #1 - workaround '..<'
default:                                         compressedValue = signedValue
}

Assume signedValue is of Type Int64 and the input value is = 10_000_000_000. This will lead to a runtime error (in Playground):

Execution was interrupted, reason: EXC_BAD_INSTRUCTION ...

Could anyone help me out to understand what exactly is happening here. There is an workaround for this problem. Instead of '...' I could use '..<' but this not how the range should be.


Solution

  • Unlike intervals (which have two flavours, half-open or closed), Range is only ever half-open. So when you write 1...5, the ...function increments the right-hand argument in order to create a half-open range 1..<6.

    The consequence of this is you can’t have a range that spans the entire length of an integer type, since 1...Int.max will result in an attempt to increment Int.max, and an overflow error. You get a similar error with let s = "hello"; let r = s.startIndex...s.endIndex

    All is not lost, however, since you shouldn’t be using ranges anyway, this is a job for ClosedInterval. Since the ... operator is used for both, and defaults to ranges (for overloading precedence reasons) you need to explicitly identify the type:

    let i64interval: ClosedInterval = Int64(Int64.min)...Int64(Int64.max)
    switch signedValue {
        // etc
        case i64interval:   compressedValue = Int(signedValue)
        default:            compressedValue = signedValue
    }