Hi there,
I have a question about Function Builders and Generics:
The TL;DR is:
I have a function builder that is generic with type constraints. The builder simply collects a variadic number of elements of a related generic type into an array.
When I use the function builder, the generic type cannot be inferred even though I would assume that this was possible.
The smallest example I could find that demonstrates the issue is:
public protocol FieldProvider {
associatedtype Field
func value(for field: Field) -> String?
}
public enum TemplateItem<F: FieldProvider> {
case staticText(String)
case field(F.Field)
}
@_functionBuilder
public class GenericFunctionBuilder<F: FieldProvider> {
static func buildBlock(_ children: TemplateItem<F>...) -> [TemplateItem<F>] {
children
}
}
struct Generic<F: FieldProvider> {
let items: [TemplateItem<F>]
public init(items: [TemplateItem<F>]) {
self.items = items
}
public init(@GenericFunctionBuilder<F> content: () -> [TemplateItem<F>]) {
self.items = content()
}
}
// Usage
struct Business {
enum Fields {
case name
}
var name: String
}
extension Business: FieldProvider {
typealias Field = Fields
func value(for field: Business.Fields) -> String? {
switch field {
case .name:
return name
}
}
}
func createUsingEnum() -> Generic<Business> {
Generic(items: [
.staticText("Name"),
.field(.name)
])
}
func create() -> Generic<Business> {
Generic {
TemplateItem<Business>.staticText("Name")
TemplateItem<Business>.field(.name)
}
}
As can be seen in createUsingEnum()
the generic type Business
can be inferred.
But in the function builder version create()
, the type cannot be inferred and I need to spell out the full generic type.
As far as I can tell, the compiler should have enough information to infer the type.
I have tried building with Swift 5.1, the latest 5.2 snapshot and latest master branch snapshot. All with similar results.
I am curious to hear whether it ought to be possible for the compiler to infer the type - full well knowing that function builders have not yet been officially accepted and added to the language.
Or might there be some logical fallacy in what I am attempting to do?
Note that the example above is abbreviated a bit - with my small proof-of-concept I would be able to use the small DSL like this:
let t: Template<Business> =
Template {
Text("Business")
Field(.name)
SubField(employees) {
Text("Name")
Field(.employeeName)
}
}
while with my troubles with automatic inference I have to write:
let t: Template<Business> =
Template {
Text("Business") as Template<Business>.Item
Field(.name) as Template<Business>.Item
SubField(employees) {
Text("Name") as Template<Employee>.Item
Field(.employeeName) as Template<Employee>.Item
} as Template<Business>.Item
}