Xcode/Swift bug in type inference?

Hi Folks,

just came across this behaviour which seems to be wrong to me.

import UIKit

var s1:UITableViewCell.SelectionStyle? = .none

var s2:UITableViewCell.SelectionStyle? = UITableViewCell.SelectionStyle.none

print("(s1==s2)")

false

print("(String(describing: s1)) - (String(describing: s2))")

nil - Optional(__C.UITableViewCellSelectionStyle)

what has happened here is that Xcode has identified s1 as coming fron

public enum Optional : ExpressibleByNilLiteral {

/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none

...

I had assumed it would interpret it as UITableViewCell.SelectionStyle.none

Xcode Version 10.2.1 (10E1001)

so;

  1. Is this a bug? (I think it is!)
  2. If so, where is the best place to report it?

When you have an optional enum and you assign .none to it then what you’re actually assigning is Optional.none instead of the none case in the enum. This is because Optional.none has higher precedence over YourEnum.none when the type of the context is inferred.

In Swift 5.1, you will now get a warning when you do this because it makes your code look ambiguous and can lead to subtle bugs.

The workaround is to explicitly type YourEnum.none if you want to assign the none case from your enum or explicitly type Optional.none if you want to assign nil.

If you think this behaviour should be changed then feel free to file a request on bugs.swift.org!

1 Like

What you really want, with less type inference, is .some(.none). So it makes sense that writing only .none prefers the one from Optional, which is direct, instead of the nested one.

1 Like

ok - getting a warning certainly improves things, but this seems like confusing/bad behaviour to me. I'd expect YourEnum.none to be higher precedence than Optional.none

var s1:UITableViewCell.SelectionStyle?
a) s1 = .blue
b) s1 = .gray
c) s1 = .none
d) s1 = nil

looking at this code, it seems obvious what happens - but actually c behaves as a 'special case'
perhaps the root here is that .none is badly named and clashes with other enums (would .nil be better?)

teaching someone Swift, I'd have to teach 'you can just use the .xxx enum, except that if the variable is optional, then watch out for enums called .none where it will clash with the optional (which is actually an enum itself) and pick from the outer scope giving you a nil'

I see the logic here - but this is very non-obvious. IMO - languages shouldn't have hidden surprises like this where the behaviour is surprising.

thanks for the thoughts. I have submitted a bug. Will see what happens.

https://bugs.swift.org/browse/SR-10705

Not an answer to your question, but if an enumeration has a .none case then this could indicate a sensible “default value,” and the variable be made non-optional:

var s1: UITableViewCell.SelectionStyle = .none
1 Like

In my specific case, I'm using optionals to let me implement a responder hierarchy.

the default selection style is .none, but in HSTableView, you can set your selection style (and other properties) at the row, section or table level.

nil indicates 'no preference at this level - ask upwards'

I think it only is confusing just because there are two cases from different enums with the same literal 'none'. If this can be achieved with other type of enums, not just optional, then there's no universal workaround for this. A warning is a good indication for a potential bug.

I agree that it is the name clash which makes things confusing.
I don't agree with the 'only'

Optionals are a pretty core part of the language, and the fact that they are implemented as an enum is triggering the problem here. Essentially something that doesn't look like an enum is causing clashes with 'obvious' enums.

flipping the question around; How often do you think developers write :
var foo:SomeEnumType? = .none
when they actually mean
var foo:SomeEnumType? = nil

1 Like

I do it every time SomeEnumType is Optional, to make clear the difference between .none, .some(.none) and .some(foo)

While I'm not saying that what you did is wrong, this can be more expressive (and surprise free) if you model this with an "Optional-like" instead.
e.g.

enum InheritableValue<T> {
  case specified(T)
  case inherited
}

I agree that this would be a more expressive form.

I still think though, that if the answer to 'Optionals behave strangely in a .none edgecase' is 'Why not use Enum Generics', then there is a problem at a lower level.

I have been coding swift (and wrote the code with this bug) back when it was swift 1. This still caught me out. I'm pretty sure you could read the whole Swift Programming Language book without discovering that optionals are implemented as Enums with the specific gotcha of .none

You only know that the InheritableValue enum will be less 'surprise free' once you know that Swift is potentially going to surprise you with optionals. I'd say the root fix would be for optionals to be less surprising.

While I agree that this case is a bad experience, it ultimately comes from the sugar provided by automatic lifting for Optionals (i.e. you can pass a non-optional wherever you ask for one), which we wouldn't really want to miss.
What would a solution be? A compiler warning for potentially ambiguous results? It has drawbacks but it could be pitched

more chat going on here:
https://bugs.swift.org/browse/SR-10705

I don't know how the compiler works - but I think @suyashsrijan has nailed how the compiler should interpret things here

Disregarding for a moment if that would be a better solution or not, I think it's highly breaking and very unlikely to be done. It is "harmful behaviour" so it could drive a change, but I don't see this one (inverting type inference priorities) happening...
A warning though I would consider. Annoying in the cases where you know what you're doing, but the drawback is only to have to fully type the value when ambiguous. But as you wrote:

I understand that it will generate a warning in swift 5.1

If this is true, I don't see any big issues

1 Like

I agree that in general 'nil' will be much more clear than '.none'. I stand corrected.
My point was that in order to treat this particular situation as a specific issue the compiler will have to handle this, well, as a special case. This is what bugs me; I would rather prefer to handle all potential name clashes universally.

@viktorcode - optionals are already a very special case.
They have their own language syntax, chaining, etc, etc

1 Like

I get what you are saying, but adding yet another case of special treatment doesn’t sit well with me.

I feel that that is supposed to work as it works now. When I define an enum I always think of case .none as if my enum was some kind of optional.
So, just to explain:

enum TextModification {
    case none
    case uppercase
    case underline
}

struct ModifiedText {
    var text: String
    var modification: TextModification = .none
}

And there's no need to make Optional<TextModification> (TextModification?) at all, because of that .none case.

Also there's a problem with switch'es where you switch on Optional values, I prefer to use there .none instead of just nil.

I just don't think this behaviour is defensible:

var textStyle:TextModification? = .uppercase
let check = (textStyle == TextModification.uppercase) //true

var textStyle:TextModification? = .none
let check = (textStyle == TextModification.none) //false

You can explain it once you understand that an optional is implemented as an enum, but that's not something which you would know from reading the Swift guide, and someone coming from another language would definitely not expect it.

Optionals are a language feature - they happen to be implemented as Enums, but thats an implementation detail which imo should be invisible.