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

I think those two are different.

at the module/package level

here you effectively define what search path will be passed to the compiler and where it will look for modules.

on the individual import statements

while here, you prevent the compiler from looking for transitive modules that are not needed for the compilation.

Not sure the optimisation could be implemented with only the former without the latter.

Im not really arguing against @_implementationOnly.

i just think it would be useful to declare that a dependency is "implementation only"

I guess that would be more of a SwiftPackageManager feature.

Something like this in Package.swift for instance

.target(
    name: "PublicLibrary",
    dependencies: [
        .implementationOnly("MyInternalLibrary")
    ]
),

That way i can declare that i dont want users of my library to depend on that module being available just by depending on my library.

It would give me the freedom to remove that library without breaking any code.

Maybe im misunderstanding a bit what @_implementationOnly currently would do.

Am i correct in assuming that if i forget to add @_implementationOnly to a single import statement it would in fact make it no longer "implementation-only" and consumers of my library can then actually import that module.

This is already formally illegal. Clients are not to assume anything about transitive dependencies. If the module is not listed in the product definition, it is an implementation detail and should not be imported directly. (Indirect use granted through @_exported import is fine).

It is only an accident of the file system that the compiler accepts such imports on some platforms. It is a hole that will be plugged. I think an implementation even exists already, although I do not remember if it is in main yet.

So you already have the freedom to remove that library without breaking any legal code.

Yes, but forgetting is difficult because you get a compiler warning letting you know your imports are inconsistent.

2 Likes

Ah that certainly helps.

To me it still feels a bit wrong to control this behaviour via import statements and it requires quite a bit of repetition (adding @_implementationOnly to every import)

The goal of @_implementationOnly is to avoid a module being exposed to consumers right?

I was not aware of this.
In my projects im actually relying on this behavior a lot (at least for dependencies for targets within the same Package) and i assume a lot of other people do to.

Plugging that hole would be a breaking change for me.

Maybe there could be a counterpart to specifying a implementationOnly dependency that allows me to specify that a certain dependency is actually part of the api i want to vend to clients.

This would be pretty similar (if not the same) to how gradle approaches this.
There you can declare a dependency either as implementation or api which i found pretty useful.

That would be @_exported import. :wink: (This one does apply to the entire module, so it is often gathered in a dedicated place instead of scattered across the tops of various files.)

Both the @_implementationOnly and @_exported features are mostly complete. This thread is about figuring out their final spelling and how to transition, because import’s current default behaviour is less helpful than either and feels like it is occupying the spelling one of the others ought to use. (They cannot go in the package manifest, because not all libraries are packages.)

Sorry to keep digging on this.
If its not helpful please let me know

About this point

I dont know if this is a feasible solution.
I imagined that this would actually boil down to some compiler flag that is applied when compiling each file/module.

For instance it would apply something like this to specify the visibility of a module.

-SWIFT_IMPLEMENTATIONONLY <Module Name>

When this flag is applied each import of that module would implicitly be rewritten to be a @_implementationOnly import

That way it could also be specified in the package manifest, and SwiftPM would "just" have to take care to apply the correct compiler flags