@testable import problems

I have a strange problem where doing a @testable import of a module produces "undefined identifier" errors. Unfortunately, I haven't been able to reproduce in a simple enough project that I could share. I'm looking for debugging ideas in order to understand what's happening and maybe come up with a workaround.

I have module A that is tested by module B. I build A as a statically linked framework, and it includes a mix of Objective-C and Swift. When in B I do @testable import A, and try to use a class declared in one of the Swift source files from A, I get "undefined identifier". I an passing the -enable-testing flag to Swift for both A and B.

What is strange, is that if I move the @testable import A to the beginning of the file (before other import's) then swiftc compiles it successfully. I have found more problems compiling tests like these and moving the @testable import's to the start of the file doesn't always fix the problem. Sometimes adding import A before @testable import A fixes it, sometimes it doesn't.

This is with Xcode 10.3 but I can reproduce with Open Source Swift as of the swift-5.0.1-RELEASE tag. I've also added some debugging around module loading and symbol table lookups but so far haven't found much (this is what gave me the idea of re-ordering the imports).

One thing to note is that I'm building with Bazel. If I build the same project with xcodebuild then it works. I've compared the Xcode and Bazel built frameworks and can't figure out what the difference might be. It all looks correct.

Any ideas how to further debug this problem?

One situation where I know this happens is when you have a ModuleA, and a ModuleB that does @_exported import ModuleA.

Then this trips the compiler:

import ModuleB // Implicitly imports ModuleA without testability.
@testable import ModuleA // Ignored, since ModuleA already imported.

Making sure that @testable is attached to the first import of the particular module makes the errors go away:

@testable import ModuleA // Imports ModuleA with testability.
import ModuleB // Implicit import skipped, since ModuleA already imported.

P.S. This ought to just work in either order. The fact that it doesn’t is a bug in Swift.

Also @_exported messes with things further, in that it makes the import visible from the entire module, not just the file where it appears. Since that means the import order is affected by the order the files are compiled—which changes if only some files need recompiling—the import order becomes completely unpredictable. That is possibly what is causing your shuffling of imports to sometimes solve it but not consistently.

Interesting... I didn't know this about @_exported. Unfortunately, in my case I don't have any @_exported imports anywhere :pensive:

No, but likely there is something similar, where the same module is being imported multiple times, but not all of the imports are marked with @testable. The imports may be explicit or implicit (and may have something to do with the stitching of the Swift–Objective‐C boundary in your case).

@_exported isn’t the only way to cause multiple, differing imports; it is just the most common.

I suggest looking at the particular symbol whose presence is inconsistent, figuring out its home module, and scanning each client module to see if all imports of it are consistent (to uncover anything vulnerable to ordering glitches).

Thanks, I made sure to put all the @testable imports at the top of the files, before any other import's and that seems to have fixed the issue. It's still somewhat strange that Xcode seems to not have problems with this... I wonder if it does something "special" or if was just dumb luck that it worked.

Incremental builds expose a lot of order‐related bugs that go unnoticed in fresh, near‐deterministic builds. I don’t know for sure, but my guess would be that it worked simply because when you switched over to try with Xcode its cache was fresh. If you tinkered long enough with resuming interrupted builds, I suspect Xcode would eventually have run into the same issue.

But I’m glad to hear you got your project building reliably again.