Unexpected module name pollution

I developed a Swift only Module for inclusion in an App. A junior engineer had some work to do, and he added some unreviewed code. He added a type alias "public typealias Parameters = [String: String]" in a header file. We build the project and get an error in one Swift file in the main project - "typealias conflict for Parameters". Turns out that file was including the Alamofire module, which also has a "public typealias Parameters = [String: String]".


But this Swift file with the conflict - it imports AlamoFire, but not the module I created with the same line!


I had been under the impression that a public item in a module would only be exposed to classes that import that module - not to a whole app.


Did I misunderstand how this works? Or, did someone do something bizarre like include my module in the .pch file???

Replies

No, any "public" or "open" symbol in an imported module is defined globally (implicitly prefixed by the module name, of course) throughout your app target. That means you can't have duplicate symbols across modules that are imported in the target.


I'm not sure of the exact rules, but if a module is imported in one Swift file, its public symbols may be available in other Swift files that don't explicitly import it. That's in part because Swift 'import' isn't like Obj-C "#import" or C "#include".


It's also in part because Swift compilation doesn't really respect file boundaries in the way you expect. Compiling a single Swift file also means compiling at least fragments of some other files, because there are no header files to provide declarations (as in Obj-C or C). Further, it's typical for Xcode/Swift to deliberately compile multiple files together for performance reasons. (If you need pieces of file B to compile file A, and pieces of file A to compile file B, it's faster just to compile A and B together as a unit. That's the general idea, at least.)


In short, modules are imported at the level of the target, not the file, so their symbols are not allowed to conflict.

That means you can't have duplicate symbols across modules that are imported in the target.

Hmmm, that’s not quite right. Consider this test:

  1. I created a new iOS app.

  2. Inside that app I created two frameworks, F1 and F2.

  3. In F1, I added a declaration like this:

    public typealias Parameters = Int

    .

  4. In F2, I added a declaration like this:

    public typealias Parameters = String

    .

  5. Now in the app I did this:

    import F1
    import F2

    That worked just fine. So, a client can import two modules with conflicting symbols.

  6. So then I start using things:

    let i = "" as Parameters
    let i1 = 1 as F1.Parameters
    let i2 = "" as F2.Parameters

    Line 1 fails with a 'Parameters' is ambiguous for type lookup in this context error. The other two work just fine, meaning you use these conflicting symbols you just have to qualify them.

  7. I then extended this to include types in the app itself. I add to the app:

    public typealias Parameters = Bool

    .

  8. Again, you can access this via qualification, this time using the app’s module name.

    let i3 = true as MyTestApp.Parameters

    .

  9. Curiously, the unqualified usage now gives a more concrete error:

    let i = "" as Parameters

    The error being Cannot convert value of type 'String' to type 'Parameters' (aka 'Bool') in coercion. It seems that Swift is giving priority to the ‘closer’ declaration.

I’m not sure why dhoerl’s case is getting an error, but the basics seem to be working for me.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

In my own defense, I'll point out I said:


>> any "public" or "open" symbol in an imported module is defined globally (implicitly prefixed by the module name, of course)


The actual global symbols in your example are F1.Parameters and F2.Parameters, which do not conflict. (Actually, they'll be mangled versions of those, but that doesn't affect the point.)


As to what module name is assumed for symbols in any given source code context, I suspect we'd have to ask over on forums.swift.org, but (as I said) I believe it behaves like this because the compilation unit in Swift is typically bigger than a single file, but it's unpredictable how much of the source is being compiled as a unit.

I should have added more information. The problem was that the compiler could not figure out WHAT enum to use - it complained it could be AlamoFire's one, or my Module's one.


I uncovered the root cause of this issue, which only affects Swift code.


My module exposes serveral enums in the Module.h file. Since there is no forward declaration of enums, I needed to add something to the ObjectiveC interface files. At first I tried adding "<Module/Module.h> to the interface, then "@import Module;" in the implementation. When I did that I got cryptic warnings from Xcode about things being doubly defined (this was months ago). I found if I just imported the Module in the .h file, fine, everyone is happy.


But yesterday I looked - some of the ObjectiveC implementation files in the Bridging header file import the Module. Thue, EVERY Swift file is going to have the Module public declarations exposed whether they want them or not. Even if I imported just the Module.h file, they'd be seen too. [Another dev told me - "See I was right - all enums should have a 3 letter prefix!"]


Now I'm pondering how to best address this. One idea: remove all enum names from ObjectiveC implementation files and use NSInteger, then cast each method in the implementation.


Is there a better approach to this?

Your setup has a lot of moving parts and I’m having troubles understanding how they all fit together. Can you boil this down to a simple example and then explain the parts you have and how they fit together?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I will terminate this thread and start a new one, but will first create a demo project that shows the problem. Am in the middle of work project so will be a few days...


Thanks for everything so far!