Populating/accessing the payload of an enum case at runtime

Sorry I haven't responded to this question in the repo, but enums are complicated!

As you said, the first question is easy to answer, but answering the rest of them will require some cleverness. There's a fantastic writeup here: Fixing enums on big-endian systems that I'll use in answering some of these questions.

  1. "Is the current case one of the ones with a payload?"

Enum cases are ordered from payload cases to empty cases:

enum Color {
  case red
  case green
  case blue(Int)
}

// Gets ordered as the following:
enum Color {
  case blue(Int)
  case red
  case green
}

Using this, we can answer the second question pretty easily (using code from my library):

let myFavoriteColor = Color.blue(128)
let colorMetadata = reflectEnum(myFavoriteColor)!

let tag = withUnsafePointer(to: myFavoriteColor) {
  // We need the enum tag, which is the case index.
  return colorMetadata.enumVwt.getEnumTag(for: $0)
}

// This means we have the first case in the enum.
print(tag) // 0

Now that we have the tag (case index), determining whether or not this is a payload case is as simple as knowing how many payload cases there are and comparing the tag:

let isPayloadCase = colorMetadata.descriptor.numPayloadCases > tag
  1. "If so, where is that payload?"

The link I posted above explains where the payload is.

  1. "How many payloads are in this case, exactly?"

We now know that our case is a payload case, getting the payload types is just demangling the mangled name from the field descriptor:

// Remember that tag = 0 and our first record is
// Color.blue
let record = colorMetadata.descriptor.fields.records[tag]

// I force unwrap here because this is known to be a payload
// case; empty cases do not have a mangled name.

// Swift.Int
print(colorMetadata.type(of: record.mangledTypeName)!)

Had our payload case used more than one payload, then the payload type itself would be a tuple of types:

enum Foo {
  case bar(Int, String, Double)
}

...

// (Swift.Int, Swift.String, Swift.Double)
print(fooMetadata.type(of: bar.mangledTypeName)!)

So hopefully that answered all of your questions! I would also like to link swift/ReflectionMirror.cpp at main · apple/swift · GitHub because it models exactly how one is supposed to copy an enum's payload out to its own value.

2 Likes