@_exported and fixing import visibility

I'm starting to pick this up again (though I'm also still working on module stability), and it seems like we've still got some open questions.

@anandabits's concern about unwanted indirect dependencies

(which I didn't respond to back in February)

Hm. Other than the "APIs from SomeNetworkingLibrary that rely on CustomTypeKit shouldn't be visible if I haven't imported CustomTypeKit", this is pretty close to the Python/Java/C# model, i.e. the model Swift was originally aiming for. Which I think indicates the following analysis

If we wanted to stick to what we have today, we'd have two, possibly three kinds of imports:

  • implementation detail imports, not to be exposed to others except possibly as link dependencies (new, the problem I'm most trying to solve)
  • imports used anywhere in API/ABI, which must be present for a client to import this module
  • re-exported modules (optional, but highly requested and basically already working)

If we expanded that to include your "you can only use API that relies on types you've already imported"*, the list changes a little:

  • implementation detail imports, not to be exposed to others (same as before)
  • imports used in API/ABI, which may be present on the client side
  • re-exported modules
  • possibly another kind for "imports used in API/ABI that are pretty much required because without them you can't do anything, but still not re-exported"

We could leave that last kind off for simplicity's sake, though.

* This is a non-trivial amount of work, but it's at least conceptually similar to the work needed to check that types within the module are only using imports you can see.

The thing I don't like about both of these solutions is that they don't match up with C's model, but I suppose we could say "types used in @objc ways have to come from re-exported modules [possibly unless they're in a forward-declarable position]". For comparison, my proposal was a much more C-ish

  • implementation detail imports, not to be exposed to others (same as above)
  • re-exported modules

and that's it. It does close off some opportunities, but it's a way simpler model, and it defines away the need to fix the extension and operator problems.

Finally, it's also possible to add additional kinds of import in the future as long as they're not the default, which brings me to the next section…

What do we do with all the existing code that just says import ModuleA?

I'm coming around to the idea that the least breaking change says "all existing imports become re-exported; you need a new syntax to do an 'implementation detail import'". I don't love this for two reasons:

  • it's a change from the current behavior that will have observable effects (more things being re-exported)
  • it goes against the principle that anything that's part of your interface should be clearly marked as such

But anything else I can think of will result in massive churn for everyone who has already published a package that uses Foundation types.

(We could say that an unannotated import gets a warning in Swift 6 or whatever, so we're only dealing with this during the backwards-compatibility period, but I don't think we'd want to introduce a language version break just for this, so it might be a long time. Possibly forever.)

Spelling

Let's hold off on this one and pick awful underscored names for all these things for now. :-) No matter what we pick this whole effort probably needs a proper proposal anyway.

Sketch of a plan

No matter what we go with, we're going to need a way to tell what modules a public declaration depends on, so I (or someone else) can start working on that.

We will have to deal with the implementation detail module not being available, which means that anything that depends on implementation details has to degrade nicely. This is similar to some of the work we do papering over Clang importer differences between Swift versions:

  • Non-public methods in classes can be replaced with placeholders (for vtable layout).
  • Members of structs have to have their definitions around, even if they're private, because that affects the struct's ABI. :-(
  • Non-public conformances are a big mess if some of the associated types are missing and we need to think about them carefully.

I think I've written quite enough in this post, but how does this sound?

2 Likes