Should code compile or not depending on order of extensions in source code?

I first noticed this in a Cocoa App Xcode 10.1 (10B61) project. I (essentially) got a compiler error saying

:stop_sign: Bar is not a member of type Foo

although I had a file Foo.swift containing:

enum Foo { }

extension Foo {
    enum Bar { }
}

and a file Bar.swift containing:

extension Foo.Bar {
    static func baz() { print("baz") }
}

The problem turned out to be that Foo.swift happened to be listed after Bar.swift in Target -> Build Phases -> Compile Sources, and the solution was to drag Bar.swift before Foo.swift in the list of Compile Sources.


Question: Should the project compile or not depending on the order of files in the Compile Sources list?


Or (afaics) equivalently: Program a.swift compiles, but b.swift does not compile, is that intended behavior?

a.swift:

extension Foo {
    enum Bar { }
}
extension Foo.Bar {
    static func baz() { print("baz") }
}
enum Foo { }
Foo.Bar.baz()

b.swift: (same as a.swift except the two extensions have swapped places)

extension Foo.Bar {
    static func baz() { print("baz") }
}
extension Foo {
    enum Bar { }
}
enum Foo { }
Foo.Bar.baz()

$ swiftc --version
Apple Swift version 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1)
Target: x86_64-apple-darwin18.2.0
$ swiftc a.swift && ./a 
baz
$ swiftc b.swift && ./b
b.swift:1:15: error: 'Bar' is not a member type of 'Foo'
extension Foo.Bar {
          ~~~ ^
b.swift:8:1: error: type 'Foo.Bar' has no member 'baz'
Foo.Bar.baz()
^~~~~~~ ~~~

It is a known bug: SR‐631. It is labelled resolved, but I don’t think it is included in the latest release.

Rearranging the compile sources list (as you figured out on your own) is the recommended workaround.

For Xcode projects (re)generated by the package manager, the source list ends up alphabetical by file name, so the compilation order can be controlled indirectly that way (i.e. by naming them Foo.swift and FooBar.swift, instead of Foo.swift and Bar.swift).

2 Likes

IIRC the bug fix is part of the Swift 5 branch. Theoretically in Swift you shouldn't care about the order, it should just work, but there are some quirks here and there where it doesn't work (in playground sometimes).

2 Likes

The playground thing might be a different issue: the order matters in files with top-level code (playgrounds, scripts, and main.swift). But that's about keeping you from accidentally using a top-level variable before it's initialized and shouldn't have anything to do with cross-file use cases.

Jeremy and Adrian are both correct about the original issue.

1 Like

I don't know about the playground thing, but I have verified the original issue in non-command line, non-playground projects (by having to reorder files in eg an iOS project's Compile Sources-list), so just to be sure I'm not misunderstanding you:

Do you mean that my command line examples in the OP are showing expected behavior, ie that the errors for b.swift are correct?

No, sorry. The examples you showed in the first post are, as Jeremy and Adrian stated, a known issue that's fixed in the Swift 5.0 branch.

1 Like

As for extensions in playground I think the confusion is that we cannot declare any stored properties in extensions today. Due to this fact I think it's a little confusing that nested types and general extensions do not work.

extension Test {
  enum Foo {}
}

struct Test {}

If you are used to the normal behavior and do a quick playground skeleton in the same fashion but it errors out, it starts boggling your mind.

However the general idea in the main playground page is anyway that the code runs from top to bottom, so the order is kind of required there.