Moving struct to a different file causes build to fail

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?

Also asked on Stack Overflow yesterday if you want internet points: https://stackoverflow.com/questions/58868217/moving-swift-struct-definition-to-a-different-file-causes-build-to-fail

I narrowed it down a little further, you only get the error if you try to use the structs in the same file as the protocols. For example if I put

SingleFoobarContainer(object: Foobar(href: "a")).getObjects()

In the protocol file, it builds if the structs are also there, it fails to build if the structs are in a different file.

My theory is that it introduces a cyclic dependency in the Swift compiler. Something about how I'm using the generics means it needs to do a first pass in the protocol file before it can compile the struct file, but it can't do that because I'm also using the structs in the protocol file.

This problem isn't true in general, you can usually introduce cyclic dependencies between files in Swift and it will figure them out.

I've tried to simplify it as much as possible:

// some inner type
struct Foo {
    let x: Int
}

// some containers contain an array
protocol MultiContainer {
    associatedtype Inner // XXX
    var objects: [Inner] { get }
}

// some containers contain a single object, which is a special case
protocol SingleContainer: MultiContainer { // XXX
    associatedtype Inner
    var object: Inner { get }
}

// this makes the single container conform
extension SingleContainer {
    var objects: [Inner] { return [object] }
}

// move this to a different file for the build to fail
struct SingleFooContainer: SingleContainer {
    let object: Foo
}

func test() { // XXX
    SingleFooContainer(object: Foo(x: 0)).objects
}

Anywhere with "XXX" I've tried simplifying it further but the build failure no longer occurs.

  • It builds if the container protocols are specific to Foo rather than generic
  • It builds if SingleContainer has its own objects requirement, rather than inheriting from MultiContainer
  • It builds if you don't try to use SingleFooContainer in the same file as the protocols

Sounds like a compiler bug. Do you mind filing one at bugs.swift.org?

Filed: https://bugs.swift.org/browse/SR-11806

Could this be related to https://bugs.swift.org/browse/SR-4568?

It seems similar, but slightly different because the ordering doesn't seem as important. I really expected it to work if you put all of the structs and protocols in the same file and just use them elsewhere (the ordering is obvious: it should be able to compile all of the types, then use them later). But it fails even in that case.

You have to put the usage of the struct in the same place where the struct is defined.

Terms of Service

Privacy Policy

Cookie Policy