Switch crashes when a case has an IndexSet as associated value

Hi, everyone!

I don't if I'm doing this the right way.
When switching on a enum with an associated value represented as an IndexSet, the code crashes at runtime.

import Foundation

enum Action {
  case a
  case b(IndexSet)
}

func doSomething(_ action: Action) {
  switch action {
  case .a:
    print("Perform logic when a")
  case .b(_):
    print("Delete entries when b using indexSet")
  }
}

doSomething(.a)

But it works perfectly with other types, String or Int for instance.

import Foundation

enum Action {
  case a
  case b(String)
}

func doSomething(_ action: Action) {
  switch action {
  case .a:
    print("Perform logic when a")
  case .b(_):
    print("Perform logic when b using String")
  }
}

doSomething(.a)

Any pointer would be much appreciated.

1 Like

You've barely scratched the surface of bugginess.

Take the following.

enum E {
    case a
//    case b(IndexSet)
    case c(Int)
}

func moo(e: E) {
    switch e {
    case .a: print("a")
//    case .b: print("b")
    case .c: print("c")
    }
}

moo(e: .a)
//moo(e: .b(IndexSet()))
moo(e: .c(4))

The output is as-expected:

a
c

Uncomment all references to case b except the call to moo(e: b(IndexSet())) and the output becomes:

a
a  // 😱
2 Likes

Reminds me of this ancient (fixed) bug [SR-8341] Finish recursive metadata support for resilient types · Issue #50869 · apple/swift · GitHub concerning resilient types.

Thank you for you answer!
Wow :hushed: the double a as output was unexpected.
So the compiler compiler considers case a as the right match even when we explicitly provide case c as input to the function?
So I wasn't doing anything weird then.
Is IndexSet the culprit?
Does it mean I have to find a work around?
Given that I need to use IndexSet inside my enum?
Or maybe I'm completely missing the point.

It appears that the compiler can't handle having IndexSet as an associated value. I just found this manifestation while researching the crash you reported. The crash also reproduces if you uncomment the middle call to moo(e:).

1 Like

Thanks! I'll make sure to read more about it.

Oh I see. Thanks again for you clarification.

Temporary workaround indices from the original IndexSet are converted to [Int] then converted back to IndexSet when needed.

import Foundation

enum Action {
  case add(Int)
  case delete(indices: [Int])
}

func doSomething(on values: inout [Int], action: Action) {
  switch action {
  case let .add(value):
    values.append(value)

  case let  .delete(indices):
    let indexSet = IndexSet(indices)
    values.remove(atOffsets: indexSet)
  }
}

var values = [1, 2, 3, 4, 5, 6, 7, 8, 9]

doSomething(on: &values, action: .add(10))

let indexSet = IndexSet([2, 3, 4, 7])
let indices: [Int] = indexSet.map { $0 }

doSomething(on: &values, action: .delete(indices: indices))