5

OK... this is difficult to explain (and to come up with a title for) but I'll try my best.

We first discovered this when using Carthage to import stuff but setting up a sample project in Xcode (without using Carthage) it seems to have done the same thing.

First, here's a screenshot of the sample project we set up...

enter image description here

The have a target Test20000 and it has a dependency A.

The A framework then has a dependency on B.

Important

The Test20000 app does NOT add B as a direct dependency.

The frameworks

In B there is a struct like...

import Foundation

public struct BType {
    public let value = "Hello, B!"
}

In A there is a file like...

import Foundation
import B

public struct AType {
    public let value = "Hello, A!"

    public func doAThing() {
        print(BType().value)
    }
}

The App

Now in the Test20000 app we do something like...

import Foundation
import A

struct TestType {
    func doSomething() {
        let aType = AType()

        print(aType.value)
        aType.doAThing()
    }
}

This works as expected. It prints...

Hello, A! Hello, B!

If I change the function to something like this...

import Foundation
import A

struct TestType {
    func doSomething() {
        let bType = BType()

        print(bType.value)
    }
}

Then this doesn't compile as B is not imported and so BType can't be accessed.

The catch!

However! If you declare an extension in B something like...

extension String {
    func doAThingInB() {
        print(self)
    }
}

Then now... without any changes to the imports and the dependencies, I can now change my app code to...

import Foundation
import A

struct TestType {
    func doSomething() {
        "Hello, bug!".doAThingInB()
    }
}

And this will print out as though the extension is public to the actual App. It sort of "bunny hops" from B, over A and into the app.

I feel like this shouldn't happen at all.

We can't find a way to turn this off or to stop this happening.

Is this a bug?

Is there something we need to do to stop this?

Thanks

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Fogmeister
  • 76,236
  • 42
  • 207
  • 306

2 Answers2

1

I have tried to make use of private module maps to hide the inner framework from being visible to the users of A, but had no luck with it. Could be related to [SR-2896] Private modulemaps do no work properly.

I guess this is the expected behaviour right now. There are multiple proposals on swift.org forums to implement something like you want, for example Namespaces x submodules or a more relevant one @_exported and fixing import visibility.

Relevant parts from the last one:

Today’s Swift is designed more like Java or C# or Python, in that if you import Bar in the implementation of Foo it doesn’t affect clients who import Foo. Or, well, it doesn’t make the top-level names of Bar visible to clients who import Foo.

  1. You still need Bar around, because the compiler doesn’t track whether you’ve used one of its types in Foo’s public interface. (That’s the previous section.)

  2. Extensions in Bar are still made visible to clients who import Foo, because the compiler doesn’t distinguish where extensions come from today.

  3. Operator declarations in Bar are still made visible to clients who import Foo, because the compiler finds operators in a different way than it finds everything else at the top level.

And from one of the last messages:

the general outcome of this discussion is that it's probably not worth doing anything clever for Swift vNext: just add "implementation-only import" and maybe "exported import" and leave the rest alone for now.

I'd be happy to know if there is a workaround for this, but it seems like there is none.

Community
  • 1
  • 1
pckill
  • 3,709
  • 36
  • 48
1

OK... so response from the Swift team...

https://bugs.swift.org/browse/SR-9913

This is something they have known about for a while. And they are not looking to fix it any time soon.

So, yes, it's a bug. But, no, there isn't any way to fix it.

I guess fixing it would cause more issues than it would fix in terms of breaking changes.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306