Enable module names to be type aliased

Is the ability to type alias module names explicitly not supported or is it just something that hasn't been explored yet?

2 Likes

What would you use it for? Languages which support something like this typically have nested modules, but Swift doesn't have submodules, and the default import statement already brings in scope all visible items from the imported module.

I imagine that primary use case for this would be that you want to explicitly qualify the module name when referring to some declaration, to clarify where it is coming from, but the module's name is very long. But Swift modules generally have short names.

Naming conflicts when two imported frameworks share a class with the same name.

For a class, you can create local typealiases once and refer to them.

typealias FMyClass = ModuleF.MyClass
typealias GMyClass = ModuleG.MyClass

Arguably, this is less aesthetic than F.MyClass and G.MyClass but it gets the job done.

1 Like

I know. What I want to know is what I asked in the first post.

1 Like

Also reusing XCTests from different targets that use @testable import <module_name>

1 Like

I just ran into a problem where I can't disambiguate the protocol SwiftUI.App from my module which is named App. I need to refer to my module, however, since I've also imported SwiftUI in the same file, I get the protocol instead of my App. If I could import SwiftUI.App as SwiftUIApp then presumably referring to App.MyType would once again work. Alternatively being able to refer to <the_current_module>.MyType would be a solution e.g. import self.MyType (where self refers to this module). Another option might be import module App since you can already e.g. import protocol SwiftUI.App to explicitly state you want that one.

1 Like

Why can't you use <AppModule>.App?

And I thought local types were supposed to take precedence in cases like this, but perhaps the rules are more complicated than that.

My <AppModule> is named App. So when I try to refer to it, there's ambiguity. I want App.ViewState, but App is also a protocol in SwiftUI so I get the protocol not my App module.

1 Like

I think the suggestion here would be to simply rename your module, since your app name isn't actually App. It's unlikely the language would be changed to support something that's more properly dealt with directly.

I think the suggestion here would be to simply rename your module, since your app name isn't actually App .

That is a solution, sure. But it's kinda akin to "you're doing it wrong". I don't think the language should have an opinion on my choice of naming. Consider that my app name might conflict with the name of a dependency so I can't simply use my app name either. Because of a language limitation, I essentially have to come up with a nonsense name. This is not ideal. Other languages support referring to the current module in order to disambiguate symbol paths. This is absolutely a minor shortcoming of Swift's design.

It's unlikely the language would be changed to support something that's more properly dealt with directly.

"more properly"? huh. I don't really think there's a proper way to enforce who gets which module names so the natural solution is to provide a mechanism to disambiguate, just like swift does with import <protocol, class, struct, etc.> Foo.Bar. The fact that this is in the language in the first place seems to indicate that there's a need to disambiguate. Where we end up if we were to take your stance is that there can be no name conflicts ever, which is simply unenforceable and impractical.

It's also a little silly, consider someone who named their app first and then later wants to bring in a dependency of the same name. "Oh sorry you must be doing it wrong obviously rename your app..." Not a great UX.

4 Likes

The original point of this thread was a suggestion to enable typealiasing module names. Rather than doing that in your code, why not simply rename your module so it doesn't conflict and you don't need the modulealias in the first place? Not only is that something simple you can do now, it would lead to better code. You haven't presented a very motivating use case.

The original point of this thread was a suggestion to enable typealiasing module names. Rather than doing that in your code, why not simply rename your module so it doesn't conflict and you don't need the modulealias in the first place?

Because renaming is not a solution to the problem. It only delays the problem and causes naming stink. And as I've subsequently demonstrated, there are 100% valid scenarios where a module name can become shadowed that you don't have control over, such as if you import some other module that isn't name the same as yours but which does have public type which shadows your module name. AFAIU it is impossible to disambiguate in that case because you can't say, "start at this module irrespective of the name and then look for the type Bar".

Not only is that something simple you can do now, it would lead to better code. You haven't presented a very motivating use case.

It is not obvious at all that encouraging a rename to MyNameThatReallyCantEverConflictSoHelpMeFoo.Bar is preferable to import Bar from Foo or import self.Bar.

Honestly the fact that other languages solve this without really any additional complexity should be motivating enough. It's a very common problem to be dealing with two types that alias each other in the same source file. In most any other language when you run into that problem you can lookup the rules for importing code and easily find support for rebinding an imported name to another name for the scope of the source file and/or syntax for referencing type names absolutely. Go, Rust, Python, Ruby, Javascript, Kotlin, Java, Haskell, (to name some popular ones) all support either rebinding names at import time or importing unambiguously by absolute path, or both.

Given a current module Foo importing OtherModule, none of

import OtherModule.Foo as NotFoo
import Qux from self
import self.Qux

seem like they would do anything other than offer people flexibility without polluting the language aesthetically or cognitively and provide users an out when faced with a scenario where aliasing occurs.

2 Likes

The real solution here is a way to specify that “this particular identifier is fully qualified, meaning it begins with a module name”.

And yes, in the case of two modules with the same name, it would be useful to be able to import one as a different name.

4 Likes

I think the plan is roughly:

  1. Finalize unique package identifiers, possibly as part of the registry work, to ensure every package's module's can be uniquely referenced.
  2. Create special syntax for module disambiguation and reference. Module::Type is usually pitched, I hope for something else.
  3. If still necessary, create a way for package consumers to change the module name of a package. I haven't seen much discussion yet, but I think I'd prefer a way for packages to declare the new name rather than it being part of the import statement. That way you get a single consistent name throughout your project.

These all seem rather low priority at the moment, but we'll see.

This is a really strange position. App didn’t always exist in SwiftUI; when it was added, that was compatibility-breaking for any project that happened to have a module named App.

Of course, this applies to any new type added anywhere, so adding a new type to any public module is now a potentially source-breaking change that requires downstream consumers to rename their modules.

According to Jon’s analysis, those people are just wrong for having used module names that other people later want to use as type names.

(Adding conflicting type names can also break downstream consumers, but in that case we have a relatively low-impact workaround: use fully qualified names, possibly using a typealias.)

Perhaps we should adopt an Objective-C style namespacing approach for modules, where everyone puts their initials at the beginning of module names?

6 Likes

Please no to the initials approach for name spacing- it’s definitely not one of the missed features from objc… :slight_smile:

9 Likes

Our use case for type-aliased modules:

We are generating our api calls via Open API spec, which results in a single file of generated structs and api functions. In this file there's many explicit references in order to eliminate disambiguation, i.e. OurMainModule.User.

We just added a new target to our project, NotificationContent and attempted to include our api code in its Target Membership. This results in compile-time errors for the new target, because OurMainModule.User doesn't exist -- instead it is expecting NotificationContent.User.

With module aliases this would be a one line fix:

modulealias OurMainModule = NotificationContent

Otherwise, we're actually stumped on how to fix the issue. We can't really duplicate the file because it's generated and will quickly become out of date. Maybe we can write some extra code to generate two separate files that each use the respective Module names, but this may not even be possible with the code gen system we're using.

You can put all your generated types in an enum “namespace”, and use that for qualification instead of the module name.

1 Like

enum namespace doesn't work for protocols. you can underscore the protocol and use a typealias in the namespace, but this will cause the protocol and all its requirements and extension members to disappear from IDE/documentation tooling. :(