SE-0339: Module Aliasing for Disambiguation

I meant specifically if there wasn’t a module named Garbage but you were trying to give it an alias.

Yes. You can pass -module-alias existing_name=new_name directly to swiftc.

1 Like

In such case, it will throw an error that no such module exists for aliasing.

As expected, right. Does that need to be called out in the proposal?

Yes, will add that to the detailed design for the SwiftPM section.

1 Like

If a library package adds a new module within the package, it will need to use a non-conflicting name. Even if a package uses a namespace for its targets, a conflict can still occur at the top-level and module aliases can be applied to resolve the conflict.

Let me give an example of what I mean. Imagine the following desired package graph, where every module is different:

MyPackage
  Gaming
    Sound (moduleAliases: [Utils: SoundUtils])
      Utils
    Utils
  Music
    Sound (moduleAliases: [Utils: SoundUtils])
      Utils
    Utils

With a standard dependency declaration, “MyPackage” would have two clashing “SoundUtils” and at least two clashing “Utils” modules. What should it specify in moduleAliases for one or both of its dependencies in order to resolve the problem?

1 Like

Perhaps MyPackage ought to be able to set moduleAliases: [Sound: GamingSound, SoundUtils: GamingSoundUtils, Utils: GamingUtils] on its Gaming dependency, and then those would be substituted into the module aliases of Gaming's own dependencies.

That was my intuition as a user too. But the proposal does not describe the intent in this scenario, and the PR appears to blindly collect a flat dictionary from the graph, basically with merge‐by‐overwrite. Given that the resolver still suffers from a non‐deterministic order of traversal in some parts of the code, I am not confident that it is even possible to predict the resulting module name with the pull request’s implementation. It does not need to be actually fixed before the review is accepted, but it would be good to have the intent clarified first.

1 Like

That's a good example; we should allow an override at the scope where a conflict occurs then. Will update the proposal and implementation to reflect this.

The proposal also doesn't say, what happens if I want to alias a module that is a direct dependency. E.g. if I have the following package graph:

MyPackage
  Sound
    Utils
  Music
    Utils

And a target in my package has neither the Sound nor the Music module as a dependency, but both Utils modules. How would I write that?

let package = Package(
 name: "MyPackage",
 dependencies: [
   .package(url: "https://.../Sound.git", from: "..."),
   .package(url: "https://.../Music.git", from: "...")
 ],
 products: [
   .executable(name: "MyPackage", targets: ["MyPackage"])
 ],
 targets: [
   .executableTarget(
       name: "App",
       dependencies: [
         .product(name: "Utils", package: "Sound"), 
         // .product(name: "Utils", moduleAliases: ["Utils": "MusicUtils"], package: "Music")
         // or
         // .product(name: "MusicUtils", moduleAliases: ["Utils": "MusicUtils"], package: "Music")
         // ?
       ]
   )
 ]
)

Also, just a minor criticism of the proposal text: currently the example package manifests included in the text are written in a weird mixture of JSON and Swift or something like that. It would probably be better to write them in normal Swift code, just how they would be written in a real package (also the *s around the moduleAliases parameters surprised me a bit when I first read them, I think it's better to leave them out).

2 Likes

For direct dependencies, you'd write
.product(name: "Utils", moduleAliases: ["Utils": "MusicUtils"], package: "Music"),
.product(name: "Utils", moduleAliases: ["Utils": "SoundUtils"], package: "Sound")

The proposal will be updated with the above scenario along with the format suggestion.

How much does this proposal help with version conflicts? Could an example be added to show how I use it to resolve those kinds of conflicts?

Also, is module aliasing for conflicts between versions something that can be handled by SwiftPM automatically?

For example, if both the Utils depended on AnalyticsPackage but pinned to different versions? I imagine for the same build product, no, since linking would fail for duplicate symbols or something like that. But for an Xcode workspace or a root, unifying package where the build products pin the same one dependency at different versions, this could be automatic, right?

1 Like

We will have to modify the version resolution in SwiftPM to account for aliased modules of different versions; this won't be part of an MVP though, but will be added in the future (future directions in the proposal will be updated to reflect this).

Okay. Thanks for the reply! Sounds good.

Part of why I asked is that it is mentioned as a motivation for the proposal and I was half expecting it to be solved.

That means adding/changing/removing a module alias is always SemVer major, correct?

Any change could lead to a previously resolving & compiling package graph suddenly failing to compile or resolve if I understand this correctly.

The problem being addressed is certainly significant enough, and I am glad to see that this proposal starts to tackle it. I've read through the proposal twice and followed the reviews.

I can't recall an earlier discussion phase, though, during which potential alternative designs were discussed. I do appreciate that the present solution is a fairly involved one with changes to many projects and want to be pragmatic about the feedback now that we are here. But I do have thoughts on the overall design:

I think the stated motivation is a very persuasive one, and particularly note this line:

it is important to be able to resolve the conflict without making invasive changes to the conflicting packages

Where the solution as currently proposed comes into limitations—requiring building from source, having runtime impact, etc.—they all stem from the fact that under the hood module aliasing actually triggers a very invasive change to the conflicting packages, with name mangling altered throughout.

It is not clear to me that there is no less invasive alternative but to do this, and if indeed there isn't one then modules without available source can never be aliased and not just as a temporary limitation of the implementation.

This would be particularly sad since, as the proposal states, nested namespacing or submodules could be a great long-term solution for many pieces of the motivating problem, but retroactive resolution of naming conflicts would still require a feature like module aliasing. However, this feature as currently designed specifically cannot accommodate binary-only modules that (unlike ones built from source) really cannot be altered and must have these naming conflicts dealt with retroactively.

1 Like

I definitely support this.

Ideally, packages are able to name modules without using unnecessary prefixes (which could ultimately collide anyway), and trust dependents to alias them at their discretion.

On a related but separate note, I’d also like to see submodule support in the future. Packages like Swift Collections currently use the unstable @_exported attribute to emulate this for overlays, which is hardly ideal. Once modules can be grouped in that fashion, you can use that for disambiguation as well.

import Collections.OrderedCollections

Running with that possibility for a moment: aliasing Collections.OrderedCollections need not be allowed, but how would you alias Collections without depending on the entirety of it? Would the proposed solution allow that?

1 Like

SE-0339 has been accepted with modifications.

1 Like