I'm implementing a parser using Swift's Decodable protocol, utilizing structs and generic protocols to cut down on boilerplate. But I'm running into a really subtle bug related to how I organize my source code!
I want to parse some basic types, which I've modeled like this
// used to generically check inner type
enum EntityType {
case foobar
}
// all inner types conform to this
protocol Entity {
static var entityType: EntityType { get }
var href: String { get }
}
// example of a specific inner type
struct Foobar: Entity, Decodable {
static let entityType = EntityType.foobar
let href: String
}
Then I set up some protocols describing how these types can be contained in the JSON I'm parsing:
// inner types might be in a container
// the container needs to give us the type and an array
protocol Container: Decodable {
var entityType: EntityType { get }
func getObjects() -> [Entity]
}
// some containers contain an array of objects
protocol MultiContainer: Container {
associatedtype Inner
var objects: [Inner] { get }
}
// this is how it conforms
extension MultiContainer where Inner: Entity {
var entityType: EntityType { return Inner.entityType }
func getObjects() -> [Entity] {
return objects
}
}
// some containers contain a single object, which is a special case
protocol SingleContainer: MultiContainer {
associatedtype Inner
var object: Inner { get }
}
// this makes the single container conform
extension SingleContainer {
var objects: [Inner] { get { return [object] } }
}
These protocols let me write really nice and simple container types like this:
// examples of specific container types
struct MultiFoobarContainer: MultiContainer {
let objects: [Foobar]
}
struct SingleFoobarContainer: SingleContainer {
let object: Foobar
}
And all of this works perfectly if I put all of that in a single Swift file!
BUT if I move those two container structs to a different source file (same directory/target as the other file), it fails to build (both in Xcode 10 and 11.2). I reproduced this in a fresh single-page app to confirm that the failure isn't due to some weird project configuration I had.
It says
Type 'SingleFoobarContainer' does not conform to protocol 'Container'
and if I have it insert protocol stubs it adds
func getObjects() -> [Entity] {
<#code#>
}
var entityType: EntityType
it's like the extension is lost by this being in a different file. Is this some gotcha about generic extensions that I'm missing?