Pitch: Fully qualified name syntax

Keep in mind that weirdness only effects disambiguating calls to specific implementations. Types don't have modules, so there would never be that weirdness with an import or a Type/function reference.

// Disambiguating Module/Type
import Module::Foo.Bar // ?? You're importing a Type, not a module.

let thing: Module::Foo.Bar // ✅ Bar is a Type inside Foo inside Module.
let thing: Module::Foo.Bar::SubModule // ?? how can a Type have a Module?

With this it's not possible to intermingle sigils. :: always comes before and not after .


//  vs disambiguating specific implementations
myArray.Swift::Array.map {}
myArray.Module::Foo.map()

In this case one can intermingle to their hearts content.

I'm unsure the same sigil should be used for both these things. Libraries are imported constantly and Types/libraries often have namespace collisions. Calling specific implementations has a much smaller use case and IMO should be discouraged in most cases as a poor design choice.

It would be a shame to over-complicate the syntax for 99% of use cases to support a 1% use case.

1 Like

It's a good point, but that's already hard to say what 99% use cases actually are when the usage is already in the minority.

(seems not to be required, but a nice to have) :: also doesn't work well for full-qualification when you may need to include extension constraint to specify extension.

One thing I'm still not clear on is the scope of this pitch.
Do we include only module qualification? What about protocol and extension qualification? Should it only work on inline environment, or should it also work with declaration?

OP seems to be concerned mostly with module qualification, but it could be easy to paint ourself into the corner if we're not careful.

We could extend the current `backtick` syntax for this purpose. Any backtick-quoted name with a dot could become a fully-qualified name:

  `SpaceKit.coneLaser`.`BlasterKit.recentBlasts`.`Swift.forEach` { blast in ... }
  `SpaceKit.coneLaser`
    .`BlasterKit.recentBlasts`
    .`Swift.forEach in extension Sequence` { blast in 
      ... 
    }

Unlike parens, there's no way this will be confusing with function calls.

(Currently, Swift won't accept dots, colons, spaces, etc in backticks, so this ought to break nothing.)

7 Likes
// Module A
struct Foo {}

// Module B
struct Foo {}

// Module C
import A
extension Foo {
  struct Bar {}
}

// Module D
import A
extension Foo {
  struct Bar {}
}

// App
import A
import B
import C
import D
let x: (A.Foo).(C.Bar)
2 Likes

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`

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.

2 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.

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.

4 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.

Terms of Service

Privacy Policy

Cookie Policy