Inconsistent Swift compiler behaviours for enum definitions

Using the compiler with Xcode Version 13.3 (13E113) I noticed some interesting patterns when it comes to compiling enum definitions:

  1. About indirect keyword
enum EnumA {
    case one
    case two(EnumA)
}

enum EnumB {
    case one
    case two([EnumB])
}

The compiler complains about adding indirect to EnumA, which makes sense. But why not for EnumB? Please refer the screenshot below. The difference of these two is that the type of the associated value in one is itself and another is array of itself.

  1. About ways of adding associated values
    For the following code:
enum EnumA {
    case one(value: EnumB)
}

indirect enum EnumB {
    case one
    case two(value: EnumB)
}

The compiler complains about circular reference as shown below

but no complains at all for the following code:

enum EnumA {
    case one(EnumB)
}

indirect enum EnumB {
    case one
    case two(EnumB)
}

The only difference of these two is the way of adding associated values: one is with name and another is not.

The indirect keyword is only necessary if the enum is self-referential. EnumB isn't. The associated value is of type Array. That the array will contain instances of EnumB is not relevant.

1 Like

Thanks for your reply. It makes sense. Initially I thought indirect keyword is needed for any recursively use of itself.
Any ideas about case 2?

Both versions of your case 2 (with labels and without labels) compile fine for me (I'm on Xcode 13.1 ATM).

This is broadly true for some definition of "recursive use." The real limitation here is that value types cannot have inline storage which is recursively referential, at any depth. The reason that Array doesn't cause issues is that Array stores its elements via an internal buffer which is allocated out-of-line of the value itself, and the inline storage for Array just holds a reference to the out-of-line storage.

See:

struct S {
  var a: EnumA // error: value type 'S' cannot have a stored property that recursively contains it
}

enum EnumA {
  case one
  case two(S) // error: recursive enum 'EnumA' is not marked 'indirect'
}
3 Likes

You are right. It should because of a change in Xcode 3.3 as we can see in the release note here: Xcode 13.3 Release Notes | Apple Developer Documentation

  • Improved the accuracy of leak scanning in Instruments, Xcode’s memory graph debugger, and the leaks command line. The system now scans object references inside multi-payload enum cases more accurately, allowing more precise memory leak analysis and the identification of strong, weak, and unowned reference types. (33836721)

Thanks. That explains it. I was thinking if this is just for reminding users about the performance in memory handling, a warning might be enough instead of an error for it?