Pitch: Fully qualified name syntax

I've come up against this a couple times, so thanks for addressing it!

Seems like 2a is the way to go. It's a relatively constrained issue, so it it seems like it should be something that can fixed with additions to the language with fix-it-able warnings for the places where it's ambiguous.

I would prefer some sort of symbol to indicate "treat this as a module name" over a # or @ option. As much as I'm hesitant to suggest something that looks like C++, ModuleName::ClassName seems like a good option.

Reasons:

  • It's foreign in Swift, but is a syntax that exists in other language and could be reasoned through if seen in the wild.
  • If nested modules are ever supported, the syntax would be self-explanatory while still concise: ParentModule::ParentModuleClass, ParentModule::ChildModule::ChildModuleClass
  • It would feel natural at both call site and as an import.

Hopefully there's a better sigil than :: that would probably be better. Unfortunately nothing better comes to mind immediately. I'm unsure how the C++ compatibility is coming, but I can see it potentially being used there (even if just internally).

15 Likes

I like this option well, and agree with the earlier reasoning about this type of disambiguation being rare. However, if we do want a sigil, I'd like to suggest @XCTest, as a callback to @import.

Would it be possible to have an non-prefixed Modules discriminator? Perhaps in the future it could expose API to see what modules are currently visible, but for now just be used to namespace the available modules? Modules.XCTest seems fine to me.

1 Like

Perhaps using backticks to refer to a module explicitly would fit the language: `Module`.Something.
Swift already uses backticks to remove the ambiguity from using names that collide with keywords. So this could feel natural, as it would also remove ambiguity - just in the context of modules names.
Not sure if this could lead to any collisions with the already existing backtick functionality though.

1 Like

Yeah, that would be the problem—it would change the meaning of existing code. I’d wager that at least a third of backtick uses are on unqualified names.

3 Likes

I agree with this - the requirement, the use of :: and the reasoning for doing so. I don’t think we should avoid something just because it looks a bit like C++ !

3 Likes
ModuleName::ClassName

While this looks nice, I don't think solving this problem is worth introducing a new scoping operator in Swift. Things are much simpler as they are now with a dot (.) everywhere.


The problem at hand only requires a way to refer to a module unambiguously. Any variation of this would work fine:

#.ModuleName.ClassName

where # acts as some sort of language root to which modules belong to.

8 Likes

If we want our design to be able to accommodate the possibility of submodules, a new scoping operator (either in name or in effect) is actually inevitable. It's just a question of how clunky it appears.

Consider that submodules may themselves need disambiguation; if we go with your idea, we have:

#.ModuleName.#.SubmoduleName.ClassName

...and then .#. becomes a new scoping operator in all but name (particularly if we subscribe to @anandabits's point about option 2a versus 2b).

Likewise, if we go with something like Modules, then we have:

Modules.ModuleName.Modules.SubmoduleName.ClassName

...and then .Modules. becomes a new scoping operator in all but name.


Compared to these options, the far superior choice in my view is ::; it's terse but not to the point of confusion, visually distinct as an actual operator that can be documented and taught, and precedented in other languages:

ModuleName::SubmoduleName::ClassName
11 Likes

I’m in favour of ::, but I don’t think it makes sense to introduce that while preferring Module.Name, so I see it as a 2b option.

I’d prefer

Module.SubModule::ClassName
7 Likes

Would the current naming system work in the presence of submodules, save the OP issue, if a module is not allowed to have a submodule and a top level type with the same name?

I would think so, and if so we would only need to create a way to name the (current) true top level, i.e. uniquely name the namespace of currently imported modules. (OP syntax 3)

1 Like

The more I think about this problem, the more I like :: as “explicit module qualification” syntax.

It seems like we could use the same syntax for module qualification of members as well:

myStr.Foundation::range(of: “hi”)

This would also solve the very real problem of 2 dependencies introducing the same overload with no way to disambiguate.

38 Likes

I think this deserve more thought. The reason you need two scoping operators is so you can avoid clashes with identifiers coming from two different uncoordinated sources. Wouldn't it make sense for submodules to be coordinated with the parent module so they don't cause clashes?

1 Like

I think it should be possible to reduce the use of some form of scoping operator to only every need to occur once in such a chain.
In your example # is used as a kind of prefix. If we instead used it as a suffix, it would be clear that everything preceding the # are module names, and everything following it are member names.
E.g. ModuleName.SubModule.#.Member.Something would unambiguously state that ModuleName and SubModule are modules, and Member and Something are members.
In a way # would then act as a member-qualifer rather than a module-qualifier.

Is there a reason we would need a specific scoping operator, rather than just disambiguating by name, perhaps with a reserved top level indicator (Modules)? For import statements I would think that would work fine for both top level and theoretical submodules. e.g. import Modules.XCTest, import Modules.XCTest.Submodule. And if we disallow inline usage except in cases of required disambiguation, I don't think we need much in the way of special behavior.

1 Like

In that case Modules. becomes the scoping operator. I believe it would also reserve the name "Modules" from being used as...pretty much anything. Type name, Module name, even Variable name.

IMO if the desire is to add any new operator, #module(ModuleName) would be the way to go

1 Like

I am more inclined towards:

I prefer $modules as the top level (or root) indicator. The rule will be: If there is an ambiguity, provide the full path: $modules.XCTest.... and I think it should be possible to provide fix-it for it.

I don't think we should let ad-hoc definition of submodules. This eliminates the possibility of module level declaration clashing with submodule name. They should be both under the control of the same module developer and the compiler should simply reject such name clashes.

Moreover, one day we may need some other top-level indicator. For example to support dynamically loaded or injected modules (ensuring they won't clash with statically present modules)

What about treating XCTest.foo as an overload, consisting of the top-level foo and the class member foo?

That's potentially a source-breaking change, and it doesn't solve the underlying need for an unambiguous syntax even if it makes it less likely.

For the specific case of .swiftinterface files, where every name is fully qualified anyway, one option may be to change import to not add any of the imported module's members to the default namespace at all. (Although I guess this would only work if local definitions get fully qualified, too.)

For the general problem, my exotic syntax idea is to use regular parens and juxtaposition:

(XCTest)Something
myString.(Foundation)range(of: "hi")

(Module.Submodule)member
thing.(Module.Submodule)ambiguousOverload

I find having the delimiters makes these easier to parse*, especially in Harlan's overloading example -- they are clearly little annotations on the names that follow them, and they are visually modest enough not to overwhelm the source text.

* That is, easy to parse for me as a human being, not necessarily the compiler.

1 Like

Unfortunately that's only true of declarations. For expressions in an interface file, like inlinable functions and default arguments, we still have plain source code as written by the user.

2 Likes