[Pre-Pitch] Import access control: a modest proposal

Correct—this change would be at compile-time. You’d need a newer compiler at your desk, but not a newer device in your users’ hands.

Probably, but I don’t think we’d actually want the compiler to infer public import any more than we’d want it to infer public class or public struct when you used a type in a public API. Your dependencies are an externally visible aspect of your module; explicitly controlling that aspect is a feature, not a bug.

Huh, that could actually be useful. I think it might be better as a separate proposal, though.

I am very much thinking about solutions to this problem, but it’s a separate issue from what we’re discussing in this thread.

One of the reasons I think we can get away with this change is that in app targets, you almost never need to write public import.* If Swift 6 mode warns about un-annotated imports, the proposal changes from “some imports, mostly in libraries, need to be marked with an additional keyword” to “every import in every file needs to be marked with an additional keyword”, and given how long it took to get from Swift 5 to Swift 6, waiting until Swift 7 to finish the transition could mean we spend several years in that state. I think that might tip this change from a minor inconvenience to a major hassle.

* It actually just occurred to me that even in app targets, retroactively conforming types you don’t own to protocols you don’t own would require a public import of both modules. This is arguably a feature.

My back-of-the-envelope hypothesis is that more than half of imports in libraries will need to become public imports, but very few imports in executables or test suites will need to change. This is not based on any data; I should probably instrument the compiler in a branch (e.g. repurpose the existing @_implementationOnly checking to emit a remark on normal imports that would need to become public) and then build the Source Compatibility Suite to get some numbers.

This is a really good question that I haven’t even thought about.

This is true. I guess my mental model is that <access-level> import M doesn’t mean “make this import visible at <access-level>; it means “import public declarations from M into this file as though they have <access-level> or less”. That is, (absent @exported) import is always inherently a per-file thing, and the access control keyword is being transitively applied to what’s being imported. (Although that gets a little weird with open.)

Incidentally, I learned recently that @hamishknight at least partially fixed the operator visibility logic last year, but there’s some compatibility logic to retain the old behavior and a feature flag to disable it.

8 Likes