[Pitch] Enum Case Inferencing

I think many developers don't know what compiler exactly doing and cannot infer whether the code is inferrable for compiler or not. It's better to use some attribute like @deterministic_getter . It's far simpler to disallow all getter, though.


Anyway, maintenance would be more difficult, and that lead us causing bugs. After this simple refactoring, string still returns .some("Hello!"), but on check site, the code becomes broken.

// before
let string: String? = "Hello!"
if string != nil {
    // here string1 is unwrapped.
    // so, this would work as String.map, and results 'HHeelllloo!!'
    print(array.map{ $0 + $0 })
}
    
// after
var string: String? {
    "Hello!"
}
if string != nil {
    // here string2 has getter and cannot be unwrapped.
    // so, this would work as Optional.map, and results 'Optional("Hello!Hello!")'
    print(array.map{ $0 + $0 })
}

Currently, change variable from let stored property to computed property causes no bug. But with non-optional inferencing, it starts affecting. For limited convenience, it has just made the maintenance much harder.

IMHO, current design of Swift makes it ignorable whether a get-only variable is let stored property or computed property. Adding non-optional inferencing which only possible when variable isn't computed property doesn't match this design.

On top of that, changing from let to computed getter is a binary-compatible change, by design. It would be another source of bugs if code compiled against a binary framework broke because the framework author changed a property to have a computed getter (or the reverse).

1 Like

We can do this:

func useFooBar(fooBar: FooBar) {
  let s: String
  switch fooBar {
  case .foo(let string): s = string
  case bar: return
  }
}

I don’t think this is a good example. The issue isn’t that they shadowed a “wrapped” variable with an “unwrapped” variable, the issue is they used the same variable name for two different values. The example could easily have been:

func expectFailure<T>(of operation: ((T, Error) -> Void) -> Void, withError error: Error) -> XCTestExpectation {
    let exp = expectation(description: “async operation failed”)
    operation { value, error in
        if error.localizedDescription == error.localizedDescription {
            exp.fulfill()
        }
    }
    return exp
}
1 Like

I think you're spot on with your analysis.

Kotlin has its own version of 'case inferencing', called smart casts and it has the exact limitations that you point out. The only times it can be expected to work is with function parameters (which are immutable) and function local variables (which can't be mutated by other scopes).

The workaround, for what is the majority of use cases, is to create local variables of all values that need smart casting, and then perform the test:

val foo = bar.foo
if (foo != null) {
    baz(foo)
}

Which in Swift, can already be more succinctly expressed as:

if let foo = bar.foo {
    baz(foo)
}

In my opinion, the let part of if let syntax is critical to both how it works and explaining why it works and shouldn't be sugared away.

7 Likes