Get rawValue from enum in a generic function

Update 8/28/2015: This will be resolved in Swift 2

See Twitter answer from Swift compiler developer

Update 10/23/2015: With Swift 2 generators, you still can't get rawValue. You can get the associated value.

The original question:

I have some general reflection code written by quick. In this code, I cannot get the value of the properties based on the enumeration. The problem boils down to the fact that I cannot execute .rawValue for a property value of type Any . Reflection Swift code will return the value of the enumeration as type Any . So, how can I get from Any to AnyObject, which is a rawValue enumeration.

The only workaround I have found so far is to extend all enumerations with the protocol. Below you can see the unit test that is OK using this workaround.

Is there a way to solve this problem without adding code to the original enumeration?

for my reflection code I need a getRawValue method signature so that it getRawValue that way.

 class WorkaroundsTests: XCTestCase { func testEnumToRaw() { let test1 = getRawValue(MyEnumOne.OK) XCTAssertTrue(test1 == "OK", "Could nog get the rawvalue using a generic function") let test2 = getRawValue(MyEnumTwo.OK) XCTAssertTrue(test2 == "1", "Could nog get the rawvalue using a generic function") let test3 = getRawValue(MyEnumThree.OK) XCTAssertTrue(test3 == "1", "Could nog get the rawvalue using a generic function") } enum MyEnumOne: String, EVRawString { case NotOK = "NotOK" case OK = "OK" } enum MyEnumTwo: Int, EVRawInt { case NotOK = 0 case OK = 1 } enum MyEnumThree: Int64, EVRaw { case NotOK = 0 case OK = 1 var anyRawValue: AnyObject { get { return String(self.rawValue) }} } func getRawValue(theEnum: Any) -> String { // What can we get using reflection: let mirror = reflect(theEnum) if mirror.disposition == .Aggregate { print("Disposition is .Aggregate\n") // OK, and now? // Thees do not complile: //return enumRawValue(rawValue: theEnum) //return enumRawValue2(theEnum ) if let value = theEnum as? EVRawString { return value.rawValue } if let value = theEnum as? EVRawInt { return String(value.rawValue) } } var valueType:Any.Type = mirror.valueType print("valueType = \(valueType)\n") // No help from these: //var value = mirror.value --> is just theEnum itself //var objectIdentifier = mirror.objectIdentifier --> nil //var count = mirror.count --> 0 //var summary:String = mirror.summary --> "(Enum Value)" //var quickLookObject = mirror.quickLookObject --> nil let toString:String = "\(theEnum)" print("\(toString)\n") return toString } func enumRawValue<E: RawRepresentable>(rawValue: E.RawValue) -> String { let value = E(rawValue: rawValue)?.rawValue return "\(value)" } func enumRawValue2<T:RawRepresentable>(rawValue: T) -> String { return "\(rawValue.rawValue)" } } public protocol EVRawInt { var rawValue: Int { get } } public protocol EVRawString { var rawValue: String { get } } public protocol EVRaw { var anyRawValue: AnyObject { get } } 
+8
reflection enums swift
source share
1 answer

Unfortunately, at the moment this is not like Swift, but I thought about your problem for a while, and I suggest 3 ways that the Swift team could allow you to solve this problem.

  • Fix mirror for listings. The simplest solution is the one that I'm sure you have already tried. You are trying to create a reflection library, and you would like to reflect the value of Any to see if this is an enumeration, and if so, you want to see if it has the original value. The rawValue property must be available through this code:

     let mirror = reflect(theEnum) // theEnum is of Any type for i in 0..<mirror.count { if mirror[i].0 == "rawValue" { switch mirror[i].1.value { case let s as String: return s case let csc as CustomStringConvertible: return csc.description default: return nil } } } 

However, this does not work. You will find that the mirror has count of 0 . I really think this is the oversight of the Swift team in their implementation of Swift._EnumMirror , and I will be presenting a radar about it. rawValue definitely a legal property. This is a strange scenario, because enumerations are not allowed to have other stored properties. In addition, your enumeration declaration never explicitly matches RawRepresentable , and does not declare a rawValue property. The compiler simply indicates that when you enter enum MyEnum: String or : Int or something else. In my tests, it seems that it does not matter if the property is defined in the protocol or is an instance of a related type, it should still be the property represented in the mirror.

  1. Allow protocol types with specific related types. As I mentioned in my comment above, this is a limitation in Swift that cannot be applied to a protocol type associated with type requirements. You cannot just use RawRepresentable because Swift does not know which type will return the rawValue property. Syntax such as RawRepresentable<where RawValue == String> is possible, or perhaps protocol<RawRepresentable where RawValue == String> . If that were possible, you could try applying to the type through a switch statement, as in:

     switch theEnum { case let rawEnum as protocol<RawRepresentable where RawValue == String>: return rawEnum.rawValue // And so on } 

But this is not defined in Swift. And if you just try to use RawRepresentable , the Swift compiler tells you that you can only use it in a common function, but looking at your code only leads you to a rabbit hole. Common functions require type information at compile time in order to work, and what exactly you do not work with Any instances.

The Swift team can modify protocols to look more like generic classes and structures. For example, MyGenericStruct<MyType> and MyGenericClass<MyType> are legally specialized specific types that you can execute and perform runtime checks. However, the Swift team may have good reason not to want to do this using protocols. Specialized versions of protocols (i.e. protocol references with known associated types) will still not be specific types. I would not hold my breath with this ability. I consider this the weakest of the solutions we have proposed.

  1. Extend protocols to match protocols. I really thought I could make this solution work for you, but alas, no. Since Swift's built-in mirror for enumerations does not apply to rawValue , I thought, why not implement my own custom mirror:

     struct RawRepresentableMirror<T: RawRepresentable>: MirrorType { private let realValue: T init(_ value: T) { realValue = value } var value: Any { return realValue } var valueType: Any.Type { return T.self } var objectIdentifier: ObjectIdentifier? { return nil } var disposition: MirrorDisposition { return .Enum } var count: Int { return 1 } subscript(index: Int) -> (String, MirrorType) { switch index { case 0: return ("rawValue", reflect(realValue.rawValue)) default: fatalError("Index out of range") } } var summary: String { return "Raw Representable Enum: \(realValue)" } var quickLookObject: QuickLookObject? { return QuickLookObject.Text(summary) } } 

Fine! Now all we need to do is expand RawRepresentable to Reflectable so that we can first cast theEnum as Reflectable (no related type is required) and then call reflect(theEnum) to give us our amazing custom mirror:

  extension RawRepresentable: Reflectable { func getMirror() -> MirrorType { return RawRepresentableMirror(self) } } 

Compiler error: protocol extension "RawRepresentable" cannot be an offer of inheritance

Whaaaat ?! We can expand specific types to implement new protocols, such as:

  extension MyClass: MyProtocol { // Conform to new protocol } 

With Swift 2, we can extend the protocols to give specific implementations of functions, such as:

  extension MyProtocol { // Default implementations for MyProtocol } 

I thought we could extend the protocols to implement other protocols, but apparently not! I see no reason why we could not get Swift to do this. I think that would fit very well into the protocol-oriented paradigm mentioned in WWDC 2015. Specific types that implement the original protocol would receive free new protocol compatibility, but a particular type can also define its own versions of the new protocol methods. I will definitely write a request for improvement, because I think it can be a powerful feature. In fact, I think this can be very useful in your reflection library.

Edit: Returning to my dissatisfaction with answer 2, I think there is a more elegant and realistic opportunity to work with these protocols in general, and this actually combines my idea with answer 3 about expanding protocols to comply with new protocols. The idea is for protocols with related types to conform to new protocols that extract the erasable properties of the original. Here is an example:

 protocol AnyRawRepresentable { var anyRawValue: Any { get } } extension RawRepresentable: AnyRawRepresentable { var anyRawValue: Any { return rawValue } } 

Extending the protocol in this way will not extend inheritance as such. Most likely, this simply tells the compiler: "No matter where the type matching RawRepresentable , this type also matches AnyRawRepresentable with this default implementation." AnyRawRepresentable will not have related type requirements, but it can still get rawValue like Any . In our code:

 if let anyRawEnum = theEnum as? AnyRawRepresentable { // Able to cast to let anyRawValue = anyRawEnum.anyRawValue // anyRawValue is of type Any switch anyRawValue { case let s as String: return s case let csc as CustomStringConvertible: return csc.description default: return nil } } 

This type of solution can be widely used with any type of protocol with the corresponding types. I will also include this idea in my proposal to the Swift protocol-wide protocol extension team.

Refresh . None of the above options are available as of Swift 4. I have not received an answer about why the Mirror in the RawRepresentable enumeration does not contain its rawValue . As for options No. 2 and No. 3, they are still within the scope for future releases of Swift . They were mentioned on the Swift mailing list and in the Manifold Manifesto document created by Doug Gregor of the Swift team.

The correct term for option # 2 ("Allow protocol types with certain related types") is a generalized existence . This would allow, possibly, to automatically transfer protocols with associated Any types, where there is a related type, or to allow syntax as follows:

 anyEnum as? Any<RawRepresentable where .RawValue == String> 

Option # 3 is mentioned on the mailing list, and this is usually a rejected feature requested, but this does not mean that it cannot be included in future versions of Swift. In the Generics manifest, it is called Conditional Compliance through Protocol Extensions . Recognizing its strength as a feature, unfortunately, it is also said that effective implementation is "almost impossible."

+10
source share

All Articles