Summary of the problem/solution
When declaring an enum with associated values, it's recommended to use a struct or a class and not a tuple.
struct Foo {
let a: Int
let b: Int
}
enum Bar {
case none
case foo(Foo)
}
When instantiating a Bar
, this becomes:
var bar1: Bar = .foo(Foo(a: 1, b: 2))
var bar2: Bar = .foo(.init(a: 1, b: 2)) // inferred
My proposition - allow omitting the type name and init call when unambiguous inference is possible:
var bar2: Bar = .foo(a: 1, b: 2)
Detailed problem description
Associated values for enums accept any type. In practice, people usually start off with a primitive value:
case foo(Int)
Extending it when necessary:
case foo(x: Int, y: Int)
And then it gets complicated. Tuples aren't really first class types, which leads to a number of quirks. Member labels are not part of the type and can get lost when passed as parameters. Destructuring occurs in some cases.
This leads a lot of developer to prefer stronger types as associated values:
struct Foo { }
// ...
case foo(Foo)
It's very common that the struct is either entirely specific to the enum case. The struct names could be long, for disambiguation and descriptiveness.
struct OneStructAssociatedValueBelongingToSomeEnum {
let x: Int
let y: Int
}
struct AnotherStructAssociatedValueBelongingToSomeEnum {
let a: Int
}
enum SomeEnumeration {
case firstEnumerationValue(OneStructAssociatedValueBelongingToSomeEnum)
case secondEnumerationValue(AnotherStructAssociatedValueBelongingToSomeEnum)
}
However, this leads to a boilerplate problem at the instantiation site.
func calculateValue(param: Int) -> SomeEnumeration {
if param > 0 {
return .firstEnumerationValue(OneStructAssociatedValueBelongingToSomeEnum(x: param, y: -1))
} else {
return .secondEnumerationValue(AnotherStructAssociatedValueBelongingToSomeEnum(a: param))
}
}
One could reduce boilerplate by converting the explicit type init to an implicit one:
func calculateValue(param: Int) -> SomeEnumeration {
if param > 0 {
return .firstEnumerationValue(.init(x: param, y: -1))
} else {
return .secondEnumerationValue(.init(a: param))
}
}
However, this does not eliminate boilerplate entirely, and makes the code less idiomatic.
Detailed suggestion
Allow the enum associated value syntax to auto-infer an initializer call, when this can be made unambiguously. Using the previous example, this would be possible:
func calculateValue(param: Int) -> SomeEnumeration {
if param > 0 {
return .firstEnumerationValue(x: param, y: -1)
} else {
return .secondEnumerationValue(a: param)
}
}
Conditions for applying the sugar:
- The associated value type has an initializer with typed and labeled argument list L
- The associated value instantiation expression has been given a tuple with the same typed and labeled argument list L
- Applying the sugar will not cause ambiguity (e.g. another initializer with non-required parameters)
Effect on source compatibility
None, the change is additive
Effect on ABI compatibility.
None, the change is additive and is syntactic sugar only.