Peculiar inheritance of enum

It turns out that Swift 3 nested enums combine options, but it's not inheritance. It's a little know "feature" you should be aware of when designing API with Swift 3.

I didn't know that, and my (I have to admit reasonable assumption) lead to the problem that can't be easily "workaround" now.

One day I got this Natalie#90:

When trying to compile the generated Storyboards.swift, I have the error Ambiguous type name 'Segue.'

Segue is an enum type and the only possibility to make it ambiguous is when two or more segues share the same identifier (case option). This, on the other hand, should be easy to fix by simply change identifier to unique.

This is not such case!

What we deal here is two classes: Class A has defined subtype enum Reusable, with one case option1.

class A {  
    enum Reusable {
        case option1
    }
}

there is another class B that inherit from the class A with defined subtype enum Reusable, with one case option1.

class B: A {  
    enum Reusable {
        case option1
    }
}

gist

these are four types in this example

  • class A
  • class B
  • enum A.Reusable
  • enum B.Reusable

When I'm trying to use B.Reusable compiler is confused and doesn't know what to do. I am confused as well, even more.

here we go. As soon as I'm trying to use enums I run into troubles:

let _ = B.Reusable.option1  

generate compile time errors:

Untitled.swift:17:11: error: ambiguous use of 'option1'  
let _ = B.Reusable.option1  
          ^
Untitled.swift:9:8: note: found this candidate  
                case option1
                     ^
Untitled.swift:3:8: note: found this candidate  
                case option1
                     ^

waaaaaat? Recall how B.Reusable is defined. There is only one possible case for B.Reusable.option1.

That's…not how I'd expect it to behave

Here what is going on: Because B inherits from A, then B.Reusable magically inherit optiona1 from A.Reusable. With that in mind, it is possible to write the code that compile, works and doesn't makes any sense:

class A {  
    enum Reusable {
        case option1
    }
}

class B: A {  
    enum Reusable { }
}

let _ = A.Reusable.option1  
let _ = B.Reusable.option1 // wat???  

B.Reusable.option1 is never defined, but due to magic inheritance, it is available to use.

No one expects the Spanish Inquisition

We have a bug here. A bug, which made me a headache. No one expects this behavior. Distinct types should remain distinct. What next? It should be fixed (I hope it's not an intended behavior). There is bug SR-3492 reported, you can subscribe to get notified when it gets resolved.

You have been warned.

This post is a follow-up of the tweet of mine. If you like stuff like that, you should definitely follow me on Twitter.

Cheers.