Populating/accessing the payload of an enum case at runtime

Say I have an enum case as Any, and I want to use reflection and unsafe pointers to determine whether this particular case has a payload or not, and access it if it does. I need to be able to determine several things from the runtime:

  • How many cases in this enum have payloads at all?
  • Is the current case one of the ones with a payload?
  • If so, where is that payload?
  • How many payloads are in this case, exactly?

Using Echo, I can easily answer the first question. I am not sure about the rest. Echo seems to be a pretty complete representation of the data available to me at runtime—excluding the functions exposed by the runtime—so I am worried that what I'm asking is not possible at all at runtime. I'm hoping someone here can tell me otherwise.

TypeLayout.rst tells me more about how enums are laid out by the compiler, but not how to fetch that layout for any given enum at runtime, such as for enums with multiple values in a single payload, or enums with more than one payload case.

Runtime.md has some interesting funtions related to enum payloads, but I'm not sure any of them will help me.

Edit: it looks like this is something Mirror is capable of, as shown below, so it should be possible for me to do! :slight_smile:

Also, somehow I overlooked destructiveProjectEnumData in Echo's EnumValueWitnessTable.swift, so I guess it's possible he just hasn't finished this area of the library yet

Edit 2: Mirror uses swift_reflectionMirror_subscript which looks like what I need

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

No worries!

Wow, awesome stuff! Thanks! :smile: I suppose swift_reflectionMirror_subscript wouldn't be too useful for this since it's read-only?

@Alejandro Somewhat orthogonal, but do you know how to safely "copy" stuff? Is there a runtime function that will perform the necessary retains for copying an Any box?

For example, if I want to copy the payload out of an enum, I was considering writing something like this:

extension EnumMetadata {
    func getTag(for instance: Any) -> UInt32 {
        var box = container(for: instance)
        return self.enumVwt.getEnumTag(for: box.projectValue())
    }
    
    func copyPayload(from instance: Any) -> (value: Any, type: Any.Type)? {
        let tag = self.getTag(for: instance)
        let isPayloadCase = self.descriptor.numPayloadCases > tag
        if isPayloadCase {
            let caseRecord = self.descriptor.fields.records[Int(tag)]
            let type = self.type(of: caseRecord.mangledTypeName)!
            var caseBox = container(for: instance)
            // Copies in the value and allocates a box as needed
            let payload = AnyExistentialContainer(
                boxing: caseBox.projectValue()~,
                type: reflect(type)
            )
            return (unsafeBitCast(payload, to: Any.self), type)
        }
        
        return nil
    }
}

I'm not doing any memory management here. If the enum payload contains an object, or a structure with object references, those need to be retained, right? Unless the compiler would emit a retain after that unsafeBitCast, but I don't think it does, does it?

Retaining an object is simple enough, but it gets complicated when the payload is a struct, or another enum case. Do you know if either swift_assignExistentialWithCopy or swift_copyPOD or something else will do this for me?

Edit: I just realized the isPOD flag is probably used to determine when this is necessary, which was going to be my next question

Edit 2: Slava has just pointed me to the functions in the type's VWT. I am going to open a new discussion on Echo about their uses.