Pitch: Fully qualified name syntax

Based on what I've seen in Swift and otherwise, it's pretty rare calling specific implementations is needed, but you got me wondering if Swift's protocol oriented nature may change that compared to the other languages I use, so I may reconsider my stance :)

I'm pretty sure that would just be let x: C::Foo.Bar

This is a much better identifier than a preceding ModuleName:: as it doesn’t get lost visually.

However, I like michelf’s much better as it fully envelops the “thing” so it’s chunked into the same things visually as it is philosophically. I feel it also benefits from already being a Swift concept and this feel like a natural expansion of its current meaning.

Okay, then I leave it for you to extend the example so that what I wrote is necessary.

My point is I don't think it ever is...

I really, really like this idea, but it breaks down with init:

MyType.`MyKit.init`(...)  // Needs to reference an initializer
MyType.`init`(...)        // References a method/property/etc. named `init`
2 Likes

Good catch.

Maybe it could work like Markdown code spans: double the external backticks delimiters when you need to:

// calling the initializer from `MyKit` module.
MyType.`MyKit.init`(...)
// calling the `init` *function* from `MyKit` module.
MyType.`` MyKit.`init` ``(...)

I'm under the impression that this is going to be so rarely needed. Although... do we need to think about conditional extensions too?

// calling the `test` function from `MyKit` module, 
//   the one for when MyType conforms to `MyKit.Identifiable`
MyType.``MyKit.test where MyType: `MyKit.Identifiable` ``(...)

I really like it as well. It addresses the points made by @lorentey very well using a visually light and clear syntax that is already used in a relatively similar way.

The double backticks is reminiscent of how custom string delimiters work. Would it make sense to consider # in the same way it is used there?

// calling the initializer from `MyKit` module.
MyType.`MyKit.init`(...)
// calling the `init` *function* from `MyKit` module.
MyType.#`MyKit.`init``#(...)
1 Like
// Module A
struct Foo {}
// Module B
struct Foo {}
// Module C
import A
import B
extension A.Foo {
  struct Bar {}
}
extension B.Foo {
  struct Bar {}
}
// Module D
import A
import B
extension A.Foo {
  struct Bar {}
}
extension B.Foo {
  struct Bar {}
}
// App
import A
import B
import C
import D
let x: (A.Foo).(C.Bar)

Thinking about it a little more, I'm not even sure why we'd need something special to nest backticks. It's not like they can contain artibrary strings of characters. An opening ` is always followed by an identifier, and a closing ` is always preceded by an identifier. It also makes no sense to have an identifier on both sides. I think this is enough to disambiguate.

MyType.`MyKit.`init``(...)
MyType.``MyKit`.`init``(...)
MyType.`MyKit.`init` where MyType: `MyKit.MyProtocol``(...)

Double backticks are syntactically possible, but at that point, you're no longer extending the existing meaning of backticks—you're overloading them with a second meaning. The good thing about the backticks concept is that, other than init (and perhaps a couple other special identifiers), you can think of `foo` and `Mod.foo` as the same "clarify the meaning of this name" feature. If you have to nest backticks, suddenly you can't really think of them that way anymore.

3 Likes

I'm more close of seeing a backtick hell than module desambiguation....

I think now that just closing what module and type are you choosing like

`MyModule.MyType`.anyChildren

I think that way you have no doubts about which module or type is producing the anyChildren call.

1 Like

Seems we need this, just tried to use a .swiftinterface and my compile fails importing modules, it is not clear what a good solution is, since the issue is occurring in imported 3rd party modules, which would be nice to not have to modify. Any consensus on it? Is it possible to address before Swift 6?

It won’t be addressed in 5.2 or 5.3, but I still plan to work on this in the future.

As a workaround, you can build your module with these extra compiler command line flags: -Xfrontend -module-interface-preserve-types-as-written. This causes all types to be printed into the interface using the exact syntax you wrote. It can cause other problems, so please check that your module interface will actually typecheck, but it’s usually not difficult to work around those problems when they happen.

5 Likes

Ah thanks! You mentioned a flag in your original post but I wasn't sure which one it was. Hopefully this works.

I'm splitting an app into modules and I've come up with a name resolving issue quite annoying :

  • Core module which contain an operator and a struct called Profile
  • Profile module contain some arbitrary code

another module rely on using the operator so it must import CoreModule as a whole.
another module rely on the Profile struct so it must import

In this scenario I can't disambiguate the two Profile names without a second file with a type alias.
This would be easily solved by a better syntax.

Is this flag still valid? Adding it to SWIFT_FLAGS doesn't preserve the types as in the source code with Swift 5.3.1

It is still valid, but there are situations where it has no effect. In particular, derived conformances still print qualified type names, since there’s nothing you wrote.

If you’re having trouble, I would:

  1. Double-check that the flag actually appears in the command lines being passed to the Swift compiler. If not, your project may be misconfigured.

  2. Check the exact places where names are still being qualified and see if you can figure out why.

I just found that this flag work only with Release configuration.

And as you mentioned, derived conformances & also inferred types prints qualified names.

Reviving this conversation since engineering at Airbnb just hit this namespacing issue head-on.

We're leaning hard into code-generation to help reduce the amount of hand-written glue code needed to maintain an app of our size and complexity. This generated code can refer to types in other modules, and in order to avoid our generated code having ambiguous type references we opted to fully qualify our type names. However, we quickly ran into the issue described in the OP, as many of our module names collide with names of public types.

We're now in a world where our generated code is referencing unqualified names – all type names used in generated code must now be globally unique. This is a significantly less-than-ideal outcome.

tl;dr: we'd love for a solve for the problem described in the pitch to be adopted by the Swift language.

10 Likes