SE-0339: Module Aliasing for Disambiguation

Hello, Swift Community.

The review of SE-0339: Module Aliasing for Disambiguation, begins now and runs through January 31st, 2022.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When emailing me directly, please keep the proposal link at the top of the message and put "SE-0339" somewhere in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/master/process.md

Thanks for helping make Swift a better language.

John McCall
Review Manager

10 Likes

Seems mostly straightforward.

My only question is how it stacks. What happens if someone attempts to alias a module that has already been aliased lower down (deliberately or accidentally)?

1 Like

I didn’t see it mentioned specifically, but I assume that if a module name that didn’t exist was used in moduleAliases, package resolution would fail? (e.g. moduleAliases: [“Utils”: “Garbage”])

1 Like

I am also curious about @SDGGiesbrecht's question.

A related concern that the proposal doesn't seem to flag is that this doesn't affect evolution. It remains the case that adding new modules can introduce clashes where they did not exist before, and cause build breakages. Module aliases need to be applied at the scope where they cause a conflict, and that is never going to be within the package that added the new module. So projects like NIO continue to need to namespace any new targets they add.

3 Likes

Is this possible to use independent of SwiftPM?

I can answer this one. Yes, the SwiftPM support fundamentally just turns into command-line flags to the compiler, and the Detailed Design section describes how those command-line flags work.

If there are multiple aliases specified for a module, it will throw an error during dependency resolution and fail to build. If a module has been aliased lower down, there shouldn't be a need to alias it again at the top level.

1 Like

The module Utils needs to exist to be aliased as Garbage. The new name Garbage should be a new unique name that does not conflict with any of the existing module names in the dependency graph.

1 Like

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.