Swift's name lookup is…messy. Very messy. This causes a number of problems, and today I'd like to talk about a potential solution for one of them.
Swift allows names to be shadowed by declarations in a nested scope. The idea is that, if you import WidgetKit to access its Widget
type, but you yourself declare a Widget
type, you can always access WidgetKit's version by writing WidgetKit.Widget
. But what if something shadows the name WidgetKit
? Then you can't fully qualify the names in WidgetKit
at all.
That might seem unlikely, but many frameworks don't use the Kit
suffix; in particular, it's quite common for a framework to be named after a type it provides, like the XCTest
class in the XCTest
framework. If you import a module like this, you have no way to fully qualify any of its names—if you say XCTest.Something
, it will look for Something
inside the XCTest.XCTest
class.
This problem is especially severe in swiftinterface files, where we would like to fully qualify all names; XCTest and a couple other modules are currently working around this with a special flag, but we'd like to handle this properly. However, we don't want to give name lookup different semantics in swiftinterface files, and this problem does come up in developer-written code sometimes too, so some kind of language change is needed to address this problem.
Although the problem is clear, the solution is not, so I'd like to open discussion on that.
Three approaches
I can think of three ways to deal with this:
-
Change the semantics of all name lookups in some way, for instance by looking up module names before imported module contents.
-
Add a syntax which unambiguously indicates that the first identifier in a dotted name is a module name. For instance, using a straw syntax, XCTest.Something
might refer to Something
inside the XCTest.XCTest
class, but @qualified XCTest.Something
would refer to Something
inside the XCTest
module. This approach has two variants:
a. Continue to prefer the current undifferentiated names wherever possible, but provide this new syntax as an escape hatch to be used when necessary.
b. Deprecate the use of undifferentiated module names and encourage developers to use the new syntax for all fully-qualified names. We might even remove support for undifferentiated module names in a future source-breaking version of Swift.
I don't think we can seriously consider #1 for two reasons: It would be badly source-breaking and the changed semantics would probably trade precision for convenience. (For instance, you might always need to say XCTest.XCTest
to address the XCTest
class.)
Both variants of #2 seem workable, but they would have different syntactic trade-offs—if we chose 2b, we would want a short, unobtrusive syntax since it would be used frequently, whereas for 2a, we would want something longer and more self-identifying since it would be used only when necessary. 2a changes the feel of the language less, but 2b simplifies the language. I think I prefer 2a, but I could probably be persuaded otherwise.
Syntax
The other open question is, what should the "this is a module name" syntax look like? I'm prototyping this with the syntax @qualified XCTest.Something
because the compiler already understands type attributes, but there are a lot of other directions we could go:
-
Different spellings of a type attribute
-
Type attribute containing the module, like @module(XCTest) Something
-
Magic root identifier, like #Qualified.XCTest.Something
or #Modules.XCTest.Something
-
Similarly, some sort of parameterized thing, like #module<XCTest>.Something
or #module(XCTest).Something
or even #<XCTest>.Something
.
-
Sigil indicating the following is a module name, like 'XCTest.Something
or ::XCTest.Something
or ..XCTest.Something
-
Different symbol to look up a name inside a module vs. a type/instance, like XCTest'Something
or XCTest::Something
-
Straight single-quotes mean a module name, like 'XCTest'.Something
; this would obviously preclude use for character literals
I'm open to other suggestions too, so bikeshed away.