From e45da2478f856ddf8d7e1c61a22754289dc85729 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 15 Jul 2025 11:30:25 -0700 Subject: [PATCH 1/5] Add module selectors proposal --- proposals/NNNN-module-selectors.md | 647 +++++++++++++++++++++++++++++ 1 file changed, 647 insertions(+) create mode 100644 proposals/NNNN-module-selectors.md diff --git a/proposals/NNNN-module-selectors.md b/proposals/NNNN-module-selectors.md new file mode 100644 index 0000000000..587b3cf8ab --- /dev/null +++ b/proposals/NNNN-module-selectors.md @@ -0,0 +1,647 @@ +# Module selectors for name disambiguation + +* Proposal: [SE-NNNN](NNNN-module-selectors.md) +* Authors: [Becca Royal-Gordon](https://github.com/beccadax) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Bug: [swiftlang/swift#53580](https://github.com/swiftlang/swift/issues/53580) (SR-11183) +* Implementation: [swiftlang/swift#34556](https://github.com/swiftlang/swift/pull/34556) +* Review: ([pitch](https://forums.swift.org/t/pitch-module-selectors/80835)) + +Previously pitched in: + +* [Pitch: Fully qualified name syntax](https://forums.swift.org/t/pitch-fully-qualified-name-syntax/28482) + +## Introduction + +We propose that Swift's grammar be extended so that, wherever an identifier +is written in source code to reference a declaration, it can be prefixed by +`ModuleName::` to disambiguate which module the declaration is expected to +come from. This syntax will provide a way to resolve several types of name +ambiguities and conflicts. + +## Motivation + +Swift's name lookup rules promote its goal of allowing code to be written in a +very clean, readable style. However, in some circumstances it can be very +difficult to unambiguously reference the declaration you want. + +### Background + +When Swift looks up a name in your source code to find the declaration it +refers to, that lookup can be either *qualified* or *unqualified*. Qualified +lookups are restricted to looking inside a certain declaration, while +unqualified lookups search more broadly. For example, in a chain of names such +as: + +```swift +mission().booster().launch() +``` + +`booster()` can only refer to members of whatever type is returned by +`mission()`, and `launch()` can only refer to members of whatever type is +returned by `booster()`, so Swift will find them using a qualified lookup. +`mission()`, on the other hand, does not have to be a member of some specific +type, so Swift will find that declaration using an unqualified lookup. + +> **Note**: Although the examples given here mostly concern uses in +> expressions, qualified and unqualified lookups are also used for names in +> type syntax, such as `Mission.Booster.Launch`. The exact lookup rules are +> slightly different but the principles are the same. + +Both kinds of lookups are slightly sensitive to context in that, since the +acceptance of [SE-0444 Member Import Visibility](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0444-member-import-visibility.md), +they are both limited to declarations imported in the current source file; +however, unqualified lookups take *much* more than just that into account. They +search through any enclosing scopes to find the "closest" use of that name. For +example, in code like: + +```swift +import RocketEngine +import IonThruster + +extension Mission { + struct Booster { + func launch(_ crew: Int) { + let attempt = 1 + ignite() + } + } +} +``` + +Swift will look for `ignite` in the following places: + +1. The local declarations inside `launch(_:)` +2. The parameters to `launch(_:)` +3. Instance members and generic parameters of the enclosing type `Booster` + (including its extensions, superclasses, conformances, etc.) +4. Static members and generic parameters of the enclosing type `Mission` +5. Top-level declarations in this module +6. Top-level declarations in other imported modules +7. The names of imported modules + +These rules are a little complicated when written out like this, but their +effect is pretty simple: Swift finds whichever `ignite` is in the "closest" +scope to the use site. If both `Booster` and `Mission` have an `ignite`, for +example, Swift will use the one in `Booster` and ignore the one in `Mission`. + +Of particular note is the last place Swift looks: the names of imported +modules. This is intended to help with situations where two modules have +declarations with the same name. For example, if both `RocketEngine` and +`IonThruster` declare an `ignite()`, `RocketEngine.ignite()` will find `ignite` +using a qualified lookup inside the module `RocketEngine`, filtering out the +one in `IonThruster`. This works in simple cases, but it breaks down in a +number of complicated ones. + +### Unqualified lookups are prone to shadowing + +Swift does not prevent declarations in different scopes from having the same +name. For example, there's nothing preventing you from having both a top-level +type and a nested type with the same name: + +```swift +struct Scrubber { ... } + +struct LifeSupport { + struct Scrubber { ... } +} +``` + +This means that the same name can have different meanings in different places: + +```swift +// This returns the top-level `Scrubber`: +func makeScrubber() -> Scrubber { ... } + +extension LifeSupport { + // This returns `LifeSupport.Scrubber`: + func makeScrubber() -> Scrubber { ... } +} +``` + +Specifically, we say that within the extension, `LifeSupport.Scrubber` +*shadows* the top-level `Scrubber`. + +This poses certain challenges—especially for mechanically-generated code, such +as module interface files—but it's usually not completely insurmountable +because you can qualify a top-level declaration with its module name. However, +it becomes a problem if *the module name itself* is shadowed by a type with the +same name: + +```swift +// Module RocketEngine +public struct RocketEngine { ... } +public struct Fuel { ... } + +// Another module +import RocketEngine + +_ = RocketEngine.Fuel() // Oops, this is looking for a nested type in the + // struct RocketEngine.RocketEngine! +``` + +In this situation, we can no longer qualify top-level declarations with module +names. That makes code generation *really* complicated, because there is no +syntax that works reliably—qualifying will help with some failures but cause +others. + +That may sound like a farfetched edge case, but it's surprisingly common for a +module to contain a type with the same name. For instance, the `XCTest` module +includes an `XCTest` class, which is a base class for `XCTestCase` and +`XCTestSuite`. To avoid this kind of trouble, developers must be careful to +give modules different names from the types inside them—the `Observation` +module, for example, might have been called `Observable` if it didn't have a +type with that name. + +### Qualified lookups can be unresolvably ambiguous + +Extensions create the possibility that a type may have two members with the +same name and similar or even outright conflicting overload signatures, +distinguished only by being in different modules. This is not a problem for +Swift's ABI because the mangled name of an extension member includes the +module it was declared in; however, there is no way to add a module name to +an already-qualified non-top-level lookup, so there's no way to express this +distinction in the surface language. Developers' only option may be to fiddle +with their imports in an attempt to make sure the desired member is the only +one that's visible. + +### Macros don't support module qualification + +Macros cannot have members--the grammar of a macro expansion allows only a +single identifier, and any subsequent `.` is taken to be a member lookup on +the expansion--so there is currently no way to qualify a macro expansion with +a module name. This limitation was discussed during the +[second review of SE-0382](https://forums.swift.org/t/se-0382-second-review-expression-macros/63064) +and the author suggested the only viable solution was to add a new, +grammatically-distinguishable syntax for module qualification. + +### These problems afflict module interfaces, but aren't unique to them + +These issues show up most often in module interfaces because the compiler +needs to generate syntax that reliably resolves to a specific declaration, but +the rules' sensitivity to context and module contents (which might change over +time!) makes that very difficult. In practice, the compiler does not attempt to +fully account for shadowing and name conflicts--by default it qualifies names +as fully as the language allows (which works about 95% of the time) and offers +a number of (undocumented) workaround flags to adjust that which are added by a +maintainer when they discover that their module is in the remaining 5%. These +flags aren't enabled automatically, though, and they don't affect the module +interfaces of downstream modules which need to reference affected declarations. +In short, the situation is a mess. + +It's important to keep in mind, though, that this doesn't *just* affect module +interfaces and generated code. Code written by humans can also run into these +issues; it's just that a person will notice the build error and fiddle with +their code until they get something that works. It therefore makes sense to +introduce a new syntax that can be used by both machines and humans. + +### Separate modules make this uniquely severe + +While problematic conflicts can sometimes occur between two declarations in a +single module, the authors believe that per-module disambiguation is the right +approach because shadowing within a module is much easier to detect and +resolve. The developer will generally notice shadowing problems when they build +or test their code, and since they control both the declaration site and the +use site, they have options to resolve any problems that are not otherwise +available (like renaming declarations or tweaking their overload signatures). +The compiler also detects and prevents outright conflicts within a specific +module, such as two extensions declaring the exact same member, which it would +allow if the declarations were in different modules. + +## Proposed solution + +We propose adding *module selectors* to the language. A module selector is +spelled `::` and can be placed before an identifier to indicate +which module it is expected to come from: + +```swift +_ = RocketEngine::Fuel() // Picks up the `Fuel` in `RocketEngine`, bypassing + // any other `Fuel`s that might be in scope +``` + +On an unqualified lookup, a module selector also indicates that lookup should +start at the top level, skipping over the declarations in contextually-visible +scopes: + +``` +// In module NASA + +struct Scrubber { ... } + +struct LifeSupport { + struct Scrubber { ... } +} + +extension LifeSupport { + // This returns the top-level `Scrubber` + func makeMissionScrubber() -> NASA::Scrubber { ... } +} +``` + +Module selectors may also be placed on qualified lookups to indicate which +module an extension member should belong to: + +```swift +// In module IonThruster +extension Spacecraft { + public struct Engine { ... } +} + +// In module RocketEngine +extension Spacecraft { + public struct Engine { ... } +} + +// In module NASA +import IonThruster +import RocketEngine + +func makeIonThruster() -> Spacecraft.IonThruster::Engine { ... } +``` + +Module selectors are permitted at locations in the type and expression syntax +where a declaration from elsewhere is referenced by name. However, it is +invalid to use one on the name of a *new* declaration: + +```swift +struct NASA::Scrubber { // Invalid--new declarations are always in the current module + ... +} +``` + +We chose this syntax—module name plus `::` operator prefixing the name they +qualify—because `::` is unused in Swift (it can't even be a custom operator) +and because using `::` in this fashion is highly precedented in other +languages. (C++, PHP, Java, and Rust all use it to indicate that the name on +the right should be looked up inside the scope on the left; Ruby and Perl use +it *specifically* to look up declarations inside modules.) + +## Detailed design + +### Grammar and parsing + +A module selector has the following grammar: + +> *module-selector* → *identifier* `::` + +The following productions may now optionally include a module selector: + +> *type-identifier* → ***module-selector?*** *type-name* *generic-argument-clause?* | ***module-selector?*** *type-name* *generic-argument-clause?* `.` *type-identifier* +> +> *primary-expression* → ***module-selector?*** *identifier* *generic-argument-clause?* +> +> *implicit-member-expression* → `.` ***module-selector?*** *identifier*
+> *implicit-member-expression* → `.` ***module-selector?*** *identifier* `.` *postfix-expression* +> +> *macro-expansion-expression* → `#` ***module-selector?*** *identifier* *generic-argument-clause?* *function-call-argument-clause?* *trailing-closures?* +> +> *key-path-component* → ***module-selector?*** *identifier* *key-path-postfixes?* | *key-path-postfixes* +> +> *function-call-argument* → ***module-selector?*** *operator* | *identifier* `:` ***module-selector?*** *operator* +> +> *initializer-expression* → *postfix-expression* `.` ***module-selector?*** `init`
+> *initializer-expression* → *postfix-expression* `.` ***module-selector?*** `init` `(` *argument-names* `)` +> +> *explicit-member-expression* → *postfix-expression* `.` ***module-selector?*** *identifier* *generic-argument-clause?*
+> *explicit-member-expression* → *postfix-expression* `.` ***module-selector?*** *identifier* `(` *argument-names* `)` +> +> *attribute-name* → ***module-selector?*** *identifier* +> +> *enum-case-pattern* → *type-identifier?* `.` ***module-selector?*** *enum-case-name* *tuple-pattern?* + +Additionally, a new production allows a scoped `import` declaration to use a +module selector and identifier instead of an import path: + +> *import-declaration* → *attributes?* `import` *import-kind?* *import-path*
+> ***import-declaration* → *attributes?* `import` *import-kind* *module-selector* *identifier*** + +### Parsing details + +The *identifier* in a *module-selector* must be classified as an identifier +by the lexer (rather than as a keyword, wildcard, integer literal, or some +other token). Backticks can be used to escape an identifier that would otherwise +be misclassified. Dollar-sign-prefixed identifiers are not supported. + +The `::` token may have whitespace on either, both, or neither sides without +affecting how the code is parsed. + +It is never valid to write two module selectors in a row; if you want to access +a declaration which belongs to a clang submodule, you should just write the +top-level module name in the module selector. + +#### Attributes + +Built-in attributes do not belong to any module and cannot have a module +selector, so if an *attribute-name* has a module selector, it is interpreted as +a custom attribute and its argument list (if present) is parsed like an +ordinary function call. + +```swift +@Swift::available(macOS 15.0.1, *) // Invalid; `macOS 15.0.1` isn't a well-formed expression +class X {} +``` + +#### Patterns + +Module selectors are allowed in *enum-case-pattern* and in *type* and +*expression* productions nested inside patterns. However, *identifier-pattern* +is unmodified and does *not* permit a module selector, even in shorthand +syntaxes designed to declare a shadow of an existing variable. If a module +selector is needed, you must use an explicit initializer expression. + +```swift +if let NASA::rocket { ... } // Invalid +if let rocket = NASA::rocket { ... } // OK + +Task { [NASA::rocket] in ... } // Invalid +Task { [rocket = NASA::rocket] in ... } // OK +``` + +#### Operator and precedence group declarations + +The *precedence-group-name* production is unmodified and does not permit +a module selector. Precedence group names exist in a separate namespace from +other identifiers and no need for this feature has been demonstrated. + +### Effects on lookup + +When a reference to a declaration is prefixed by a module selector, only +declarations declared in, or re-exported by, the indicated module will be +considered as candidates. All other declarations will be filtered out. + +For example, in the following macOS code: + +```swift +import Foundation + +class NSString {} + +func fn(string: Foundation::NSString) {} +``` + +`string` will be of type `Foundation.NSString`, rather than the `NSString` +class declared in the same file. Because the AppKit module +re-exports Foundation, this example would also behave the same way: + +```swift +import AppKit + +class NSString {} + +func fn(string: AppKit::NSString) {} +``` + +> **Note**: Allowing re-exports ensures that "hoisting" a type from its +> original module up to another module it imports is not a source-breaking +> change. It also helps with situations where developers don't realize where a +> given type is declared; for instance, many developers believe `NSObject` is +> declared in `Foundation`, not `ObjectiveC`. + +Additionally, when a reference to a declaration prefixed by a module selector +is used for an unqualified lookup, the lookup will begin at the module-level +scope, skipping any intervening enclosing scopes. That means a top-level +declaration will not be shadowed by local variables, parameters, generic +parameters, or members of enclosing types: + +```swift +// In module MyModule + +class Shadowed { + struct Shadowed { + let Shadowed = 42 + func Shadowed(Shadowed: () -> Void) { + let Shadowed = "str" + let x = MyModule::Shadowed() // refers to top-level `class Shadowed` + } + } +} +``` + +A module selector can only rule out declarations that might otherwise have been +chosen instead of the desired declaration; it cannot access a declaration which +some other language feature has ruled out. For example, if a declaration is +inaccessible because of access control or hasn't been imported into the current +source file, a module selector will not allow it to be accessed. + +## Source compatibility + +This change is purely additive; it only affects the behavior of code which uses +the new `::` token. In the current language, this sequence can only appear +in valid Swift code in the selector of an `@objc` attribute, and the parser +has been modified to split the token when it is encountered there. + +## ABI compatibility + +This change does not affect the ABI of existing code. The Swift compiler has +always resolved declarations to a specific module and then embedded that +information in the ABI's symbol names; this proposal gives developers new ways +to influence those resolution decisions but doesn't expand the ABI in any way. + +## Implications on adoption + +Older compilers will not be able to parse source code which uses module +selectors. This means package authors may need to increase their tools version +if they want to use the feature, and authors of inlinable code may need to +weigh backwards compatibility concerns. + +Similarly, when a newer compiler emits module selectors into its module +interfaces, older compilers won't be able to understand those files. This isn't +a dealbreaker since Swift does not guarantee backwards compatibility for module +interfaces, but handling it will require careful staging and there may be a +period where ABI-stable module authors must opt in to emitting module +interfaces that use the feature. + +## Future directions + +### Special syntax for the current module + +We could allow a special token to be used in place of the module name to +force a lookup to start at the top level, but not restrict it to a specific +module. Candidates include: + +```swift +Self::ignite() +_::ignite() +*::ignite() +``` + +These syntaxes have all been intentionally kept invalid (a module named `Self`, +for instance, would have to be wrapped in backticks: `` `Self`::someName ``), +so one of them can be added later if there's demand for it. + +### Disambiguation for subscripts + +There is currently no way to add a module selector to a use of a subscript. We +could add support for a syntax like: + +```swift +myArray.Swift::[myIndex] +``` + +### Disambiguation for conformances + +Retroactive conformances have a similar problem to extension members--the ABI +distinguishes between otherwise identical conformances in different modules, +but the surface syntax has no way to resolve any ambiguity--so a feature which +addressed them might be nice. However, there is no visible syntax associated +with use of a conformance that can be qualified with a module selector, so it's +difficult to address as part of this proposal. + +It's worth keeping in mind that [SE-0364's introduction of `@retroactive`](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0364-retroactive-conformance-warning.md) +reflects a judgment that retroactive conformances should be used with care. The +absence of such a feature is one of the complications `@retroactive` is meant +to flag. + +### Support selecting conflicting protocol requirements + +Suppose that a single type conforms to two protocols with conflicting protocol +requirements: + +```swift +protocol Employable { + /// Terminate `self`'s employment. + func fire() +} + +protocol Combustible { + /// Immolate `self`. + func fire() +} + +struct Technician: Employable, Combustible { ... } +``` + +It'd be very useful to be able to unambiguously specify which protocol's +requirement you're trying to call: + +```swift +if myTechnician.isGoofingOff { + myTechnician.Employable::fire() +} +if myTechnician.isTooCloseToTheLaunch { + myTechnician.Combustible::fire() +} +``` + +However, allowing a protocol name--rather than a module name--to be written +before the `::` token re-introduces the same ambiguity this proposal seeks +to solve because a protocol name could accidentally shadow a module name. +We'll probably need a different feature with a distinct syntax to resolve +this use case. + +### Support selecting default implementations + +Similarly, it would be useful to be able to specify that you want to call a +default implementation of a protocol requirement even if the conformance +provides another witness. (This could be used similarly to how `super` is used +in overrides.) However, this runs into similar problems with reintroducing +ambiguity, and it also just doesn't quite fit the shape of the syntax (there's +no name to uniquely identify the default implementation you want). Once again, +this probably requires a different feature with a distinct syntax--perhaps +something a little more like how `super` works. + +## Alternatives considered + +### Change lookup rules in module interfaces + +Some of the problems with module interfaces could be resolved by changing the +rules for qualified lookup *within module interface files specifically*. For +instance, we could decide that in a module interface file, unqualified lookups +can only find module names, and the compiler must always qualify every name +with a module name. + +This would probably be a viable solution with enough effort, but it has a +number of challenges: + +1. There are some declarations—generic parameters, for instance—which are + not accessible through any qualified lookup (they are neither top-level + declarations nor accessible through the member syntax). We would have to + invent some way to reference these. + +2. Existing module interfaces have already been produced which would be broken + by this change, so it would have to somehow be made conditional. + +3. Currently, inlinable function bodies are not comprehensively regenerated + for module interfaces; instead, the original textual source code is + inserted with minor programmatic edits to remove comments and `#if` blocks. + This means we would have to revert to the normal lookup rules within an + inlinable function body. + +It also would not help with ambiguous *qualified* lookups, such as when two +modules use extensions to add identically-named nested types to a top-level +type, and it would not give developers new options for handling ambiguity in +human-written code. + +### Add a fallback lookup rule for module name shadowing + +The issue with shadowing of module names could be addressed by adding a narrow +rule saying that, when a type has the same name as its enclosing module and a +qualified lookup inside it doesn't find any viable candidates, Swift will fall +back to looking in the module it shadowed. + +This would address the `XCTest.XCTestCase` problem, which is the most common +seen in practice, but it wouldn't help with more complicated situations (like +a nested type shadowing a top-level type, or a type in one module having the +same name as a different module). It's also not a very principled rule and +making it work properly in expressions might complicate the type checker. + +### Syntax involving a special prefix + +We considered creating a special syntax which would indicate unambiguously that +the next name must be a module name, such as `#Modules.FooKit.bar`. However, +this would only have helped with top-level declarations, not members of +extensions. + +### Use a syntax that avoids `::`'s shortcomings + +Although we have good reasons to propose using the `::` operator (see "Proposed +solution" above), we do not think it's a perfect choice. It appears visually +"heavier" than the `.` operator, which means developers reading the code might +mentally group the identifiers incorrectly: + +```swift +Mission.NASA::Booster.Exhaust // Looks like it means `(Mission.NASA) :: (Booster.Exhaust)` + // but actually means `Mission . (NASA::Booster) . Exhaust` +``` + +We rejected a number of alternatives that would avoid this problem. + +#### Making module selectors qualify the rightmost name + +One alternative would be to have the module selector qualify the *rightmost* +name in the member chain, rather than the leftmost, so that a module selector +could only appear at the head of a member chain. The previous example would +then be written as: + +```swift +(NASA::Mission.Booster).Exhaust +``` + +We don't favor this design because we believe: + +1. Developers more frequently need to qualify top-level names (which exist in a + very crowded quasi-global namespace) than member names (which are already + limited by the type they're looking inside); a syntax that makes qualifying + the member the default is optimizing for the wrong case. + +2. The distance between the module and the identifier it qualifies increases + the cognitive burden of pairing up modules to the names they apply to. + +3. Subjectively, it's just *weird* that the selector applies to a name that's a + considerable distance from it, rather than the name immediately adjacent. + +#### Use a totally different spelling + +We've considered and rejected a number of other spellings for this feature, +such as: + +```swift +Mission.#module(NASA).Booster.Exhaust // Much wordier, not implementable as a macro +Mission.::NASA.Booster.Exhaust // Looks weird in member position +Mission.Booster@NASA.Exhaust // Ignores Swift's left-to-right nesting convention +Mission.'NASA.Booster.Exhaust // Older compilers would mis-lex in inactive `#if` blocks +Mission.NASA'Booster.Exhaust // " +Mission.(NASA)Booster.Exhaust // Arbitrary; little connection to prior art +Mission.'NASA'.Booster.Exhaust // " +``` From 98b072f224645476dcef78d530859a253debd380 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 29 Jul 2025 17:48:25 -0700 Subject: [PATCH 2/5] Minor formatting and wording improvements --- proposals/NNNN-module-selectors.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/proposals/NNNN-module-selectors.md b/proposals/NNNN-module-selectors.md index 587b3cf8ab..31a8b20c32 100644 --- a/proposals/NNNN-module-selectors.md +++ b/proposals/NNNN-module-selectors.md @@ -224,7 +224,7 @@ On an unqualified lookup, a module selector also indicates that lookup should start at the top level, skipping over the declarations in contextually-visible scopes: -``` +```swift // In module NASA struct Scrubber { ... } @@ -285,7 +285,7 @@ A module selector has the following grammar: > *module-selector* → *identifier* `::` -The following productions may now optionally include a module selector: +The following productions may now optionally include a module selector (changes are in bold): > *type-identifier* → ***module-selector?*** *type-name* *generic-argument-clause?* | ***module-selector?*** *type-name* *generic-argument-clause?* `.` *type-identifier* > @@ -326,10 +326,16 @@ be misclassified. Dollar-sign-prefixed identifiers are not supported. The `::` token may have whitespace on either, both, or neither sides without affecting how the code is parsed. +#### Syntaxes reserved for future directions + It is never valid to write two module selectors in a row; if you want to access a declaration which belongs to a clang submodule, you should just write the top-level module name in the module selector. +It is never valid to write a keyword, operator, or `_` in place of a module +name; if a module's name would be mistaken for one of these, it must be +wrapped in backticks to form an identifier. + #### Attributes Built-in attributes do not belong to any module and cannot have a module @@ -481,9 +487,9 @@ myArray.Swift::[myIndex] ### Disambiguation for conformances -Retroactive conformances have a similar problem to extension members--the ABI +Retroactive conformances have a similar problem to extension members—the ABI distinguishes between otherwise identical conformances in different modules, -but the surface syntax has no way to resolve any ambiguity--so a feature which +but the surface syntax has no way to resolve any ambiguity—so a feature which addressed them might be nice. However, there is no visible syntax associated with use of a conformance that can be qualified with a module selector, so it's difficult to address as part of this proposal. @@ -524,7 +530,7 @@ if myTechnician.isTooCloseToTheLaunch { } ``` -However, allowing a protocol name--rather than a module name--to be written +However, allowing a protocol name—rather than a module name—to be written before the `::` token re-introduces the same ambiguity this proposal seeks to solve because a protocol name could accidentally shadow a module name. We'll probably need a different feature with a distinct syntax to resolve @@ -538,7 +544,7 @@ provides another witness. (This could be used similarly to how `super` is used in overrides.) However, this runs into similar problems with reintroducing ambiguity, and it also just doesn't quite fit the shape of the syntax (there's no name to uniquely identify the default implementation you want). Once again, -this probably requires a different feature with a distinct syntax--perhaps +this probably requires a different feature with a distinct syntax—perhaps something a little more like how `super` works. ## Alternatives considered @@ -586,7 +592,7 @@ a nested type shadowing a top-level type, or a type in one module having the same name as a different module). It's also not a very principled rule and making it work properly in expressions might complicate the type checker. -### Syntax involving a special prefix +### Use a syntax involving a special prefix We considered creating a special syntax which would indicate unambiguously that the next name must be a module name, such as `#Modules.FooKit.bar`. However, From d37f657ac154026e4c43924606da18e0aedf8edc Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 29 Jul 2025 17:58:13 -0700 Subject: [PATCH 3/5] Make changes based on parsing code review --- proposals/NNNN-module-selectors.md | 45 ++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/proposals/NNNN-module-selectors.md b/proposals/NNNN-module-selectors.md index 31a8b20c32..0405ce757a 100644 --- a/proposals/NNNN-module-selectors.md +++ b/proposals/NNNN-module-selectors.md @@ -316,6 +316,9 @@ module selector and identifier instead of an import path: > *import-declaration* → *attributes?* `import` *import-kind?* *import-path*
> ***import-declaration* → *attributes?* `import` *import-kind* *module-selector* *identifier*** +Note that this new *import-declaration* production does not allow a submodule +to be specified. + ### Parsing details The *identifier* in a *module-selector* must be classified as an identifier @@ -326,6 +329,17 @@ be misclassified. Dollar-sign-prefixed identifiers are not supported. The `::` token may have whitespace on either, both, or neither sides without affecting how the code is parsed. +The token *after* a module selector is interpreted like a member reference: +keywords which would not have a special meaning in that position are +automatically treated as identifiers, even if they are not surrounded by +backticks. (Compare to [SE-0071 Allow (most) keywords in member references](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0071-member-keywords.md).) + +```swift +print(default) // Invalid; 'default' is a keyword and needs backticks +print(NASA.default) // OK under SE-0071 +print(NASA::default) // OK under this proposal +``` + #### Syntaxes reserved for future directions It is never valid to write two module selectors in a row; if you want to access @@ -651,3 +665,34 @@ Mission.NASA'Booster.Exhaust // " Mission.(NASA)Booster.Exhaust // Arbitrary; little connection to prior art Mission.'NASA'.Booster.Exhaust // " ``` + +### Restrict whitespace to the right of the `::` + +Allowing a newline between `::` and the identifier following it means that +something that was meant to be a statement-introducing keyword might be +misunderstood as an identifier instead. For instance: + +``` +let x = NASA:: +do { ... } + +// Interpreted as `let x = NASA::do() { ... }` +``` + +Swift prevents this kind of problem from happening with the member-lookup `.` +operator by making it whitespace sensitive: a newline is allowed before the +`.`, but not after it. We could impose a similar rule on `::`, but we have +chosen not to because when developers need to break up a line with a `::`, we +aren't certain that they would prefer the style it would still allow: + +```swift +SuperLongAndComplicatedModuleName + ::superLongAndComplicatedFunctionName() +``` + +Over the one it would forbid: + +```swift +SuperLongAndComplicatedModuleName:: + superLongAndComplicatedFunctionName() +``` From 78f48d4be26f074e23eb561644ac9c66a6ff3e85 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 29 Jul 2025 17:58:45 -0700 Subject: [PATCH 4/5] Clarify and expand discussions in pitch Particularly future directions and alternatives considered. --- proposals/NNNN-module-selectors.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/proposals/NNNN-module-selectors.md b/proposals/NNNN-module-selectors.md index 0405ce757a..02b7011e37 100644 --- a/proposals/NNNN-module-selectors.md +++ b/proposals/NNNN-module-selectors.md @@ -476,14 +476,15 @@ interfaces that use the feature. ### Special syntax for the current module -We could allow a special token to be used in place of the module name to -force a lookup to start at the top level, but not restrict it to a specific -module. Candidates include: +We could allow a special token, or no token, to be used in place of the module +name to force a lookup to start at the top level, but not restrict it to a +specific module. Candidates include: ```swift Self::ignite() _::ignite() *::ignite() +::ignite() ``` These syntaxes have all been intentionally kept invalid (a module named `Self`, @@ -548,7 +549,13 @@ However, allowing a protocol name—rather than a module name—to be written before the `::` token re-introduces the same ambiguity this proposal seeks to solve because a protocol name could accidentally shadow a module name. We'll probably need a different feature with a distinct syntax to resolve -this use case. +this use case—perhaps something like: + +```swift +if myTechnician.isGoofingOff { + (myTechnician as some Employable).fire() +} +``` ### Support selecting default implementations @@ -625,9 +632,13 @@ Mission.NASA::Booster.Exhaust // Looks like it means `(Mission.NASA) :: (Boos // but actually means `Mission . (NASA::Booster) . Exhaust` ``` +This is not unprecedented—in C++, `myObject.MyClass::myMember` means +`(myObject) . (MyClass::myMember)`—but it's awkward for developers without +a background in a language that works like this. + We rejected a number of alternatives that would avoid this problem. -#### Making module selectors qualify the rightmost name +#### Make module selectors qualify different names One alternative would be to have the module selector qualify the *rightmost* name in the member chain, rather than the leftmost, so that a module selector @@ -651,6 +662,13 @@ We don't favor this design because we believe: 3. Subjectively, it's just *weird* that the selector applies to a name that's a considerable distance from it, rather than the name immediately adjacent. +A closely related alternative would be to have the module selector qualify +*all* names in the member chain, so that in `(NASA::Mission.Booster).Exhaust`, +both `Mission` and `Booster` must be in module `NASA`. We think point #1 from +the list above applies to this design too: `Mission` is a sparse enough +namespace that developers are more likely to be hindered by `Booster` being +qualified by the `NASA` module than helped by it. + #### Use a totally different spelling We've considered and rejected a number of other spellings for this feature, From 19bded8073c4466f429046c65f2d6ba77d9cdc54 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Wed, 30 Jul 2025 18:03:27 -0700 Subject: [PATCH 5/5] Flip-flop on restricting newlines after `::` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If reviewers don’t like it, we can revisit the question. --- proposals/NNNN-module-selectors.md | 65 ++++++++++++++++++------------ 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/proposals/NNNN-module-selectors.md b/proposals/NNNN-module-selectors.md index 02b7011e37..42ed4b6e67 100644 --- a/proposals/NNNN-module-selectors.md +++ b/proposals/NNNN-module-selectors.md @@ -326,13 +326,27 @@ by the lexer (rather than as a keyword, wildcard, integer literal, or some other token). Backticks can be used to escape an identifier that would otherwise be misclassified. Dollar-sign-prefixed identifiers are not supported. -The `::` token may have whitespace on either, both, or neither sides without -affecting how the code is parsed. +The `::` token may be separated from its *identifier* by any whitespace, +including newlines. -The token *after* a module selector is interpreted like a member reference: -keywords which would not have a special meaning in that position are -automatically treated as identifiers, even if they are not surrounded by -backticks. (Compare to [SE-0071 Allow (most) keywords in member references](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0071-member-keywords.md).) +#### Effect on the next token + +Much like a member-lookup `.`, the presence of a module selector affects the +parsing of the token *after* it: + +* There must not be any newlines between the `::` and the next token. This + restriction aids in recovery when parsing incomplete code. + +```swift +NASA:: + RocketEngine // Invalid +NASA + ::RocketEngine // OK +``` + +* If the next token is a keyword, but that keyword does not have any special + meaning, it will be treated as an identifier even if it's not surrounded by + backticks. (Compare to [SE-0071 Allow (most) keywords in member references](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0071-member-keywords.md).) ```swift print(default) // Invalid; 'default' is a keyword and needs backticks @@ -684,33 +698,34 @@ Mission.(NASA)Booster.Exhaust // Arbitrary; little connection to prio Mission.'NASA'.Booster.Exhaust // " ``` -### Restrict whitespace to the right of the `::` +### Don't restrict whitespace to the right of the `::` -Allowing a newline between `::` and the identifier following it means that -something that was meant to be a statement-introducing keyword might be -misunderstood as an identifier instead. For instance: +Allowing a newline between `::` and the identifier following it would mean +that, when an incomplete line of code ended with a module selector, Swift might +interpret a keyword on the next line as a variable name, likely causing multiple +confusing syntax errors in the next statement. For instance: +```swift +let x = NASA:: // Would be interpreted as `let x = NASA::if x { ... }`, +if x { ... } // causing several confusing syntax errors. ``` -let x = NASA:: -do { ... } - -// Interpreted as `let x = NASA::do() { ... }` -``` -Swift prevents this kind of problem from happening with the member-lookup `.` -operator by making it whitespace sensitive: a newline is allowed before the -`.`, but not after it. We could impose a similar rule on `::`, but we have -chosen not to because when developers need to break up a line with a `::`, we -aren't certain that they would prefer the style it would still allow: +Forbidding it, however, has a cost: it restricts the code styles developers can +use. If a developer wants to put a line break in the middle of a name with a +module selector, they will not be able to format it like this: ```swift -SuperLongAndComplicatedModuleName - ::superLongAndComplicatedFunctionName() +SuperLongAndComplicatedModuleName:: + superLongAndComplicatedFunctionName() ``` -Over the one it would forbid: +And will have to write it like this instead: ```swift -SuperLongAndComplicatedModuleName:: - superLongAndComplicatedFunctionName() +SuperLongAndComplicatedModuleName + ::superLongAndComplicatedFunctionName() ``` + +The member-lookup `.` operator has a similar restriction, but developers may +not want to style them in exactly the same way, particularly since C++ +developers often split a line after a `::`.