How to access common property in enum associated values?

I'm trying to replicate some behaviour that typescript has:


interface SomeInterface {
    someProperty: string
}

interface a extends SomeInterface {

}
interface b extends SomeInterface {

}

type SwiftEnumEquivalent = a|b

function takesSomeInterface(x: SwiftEnumEquivalent){
    console.log(x.someProperty);
}

The takeaway here is the type I've defined as 'SwiftEnumEquivalent' is similar to an enum with an associated value. Now both interfaces 'a' and 'b' inherits from 'SomeInterface' which has a property 'someProperty'. When using 'SwiftEnumEquivalent' I can use the 'someProperty' as it infers that any of the types in the union have this property.

This isn't directly possible in swift. I understand you can have computed properties on enums where you can switch on all possible values of the enum and present it manually, but this is more work than what I have to do in typescript. E.g.:



protocol SomeProtocol {
      var someProperty: String {get}
}
enum MyEnum: SomeProtocol {
     case a(SomeProtocol)
     case b(SomeProtocol)

     var someProperty {
       switch self {
        ... etc here
      }
   }
}

Some I'm wondering what the technical reason that the same thing in can't be achieved in swift. I understand they're both 'sum types' but thats as far as my knowledge goes.

1 Like

Reminds me of a case I ran into where every case of DecodingError has a Context associated, and I wanted a general purpose way of accessing that, so I ended up with this:

extension DecodingError
{
  var context: Context
  {
    switch self {
      case .dataCorrupted(let context),
           .keyNotFound(_, let context),
           .typeMismatch(_, let context),
           .valueNotFound(_, let context):
        return context
      @unknown default:
        return Context(codingPath: [], debugDescription: "")
    }
  }
}

I think that's similar to what you were talking about. What do you think would be a better solution for this?

If you don't need/want MyEnum to conform you can also use @dynamicMemberLookup like this if you just need properties not functions:

protocol SomeProtocol {
    var someProperty: String {get}
}

@dynamicMemberLookup
enum MyEnum {
    case a(SomeProtocol)
    case b(SomeProtocol)

    public subscript<T>(dynamicMember member: KeyPath<SomeProtocol, T>) -> T {
        switch self {
        case let .a(someProtocol),
             let .b(someProtocol):
            return someProtocol[keyPath: member]
        }
    }
}

If you do need MyEnum to conform I would do as @David_Catmull is suggesting have a private context property that returns the SomeProtocol value and your conformances to the protocol accesses the context and maps to its implementation for the protocol like this:

enum MyEnum: SomeProtocol {
    case a(SomeProtocol)
    case b(SomeProtocol)

    private var context: SomeProtocol {
        switch self {
        case let .a(context),
             let .b(context):
            return context
        }
    }

    var someProperty: String { context.someProperty }
}