Swift 6.4 emits an unreadable module interface: module selector on a cross-module associated type
### Description
In Swift 6.4, the module interface printer qualifies an associated type with a **module selector naming the conforming type's module** rather than the module that declares the associated type. The resulting `.swiftinterface` cannot be compiled back by the same compiler that produced it.
The printer gets the protocol right (`Proto::HasElement`) and the associated type wrong (`Client::Element`) on the very same declaration:
```swift
public struct Bag<Element> : Proto::HasElement {
public init()
public func firstAssoc() -> Client::Bag<Element>.Client::Element?
}
```
`Element` is declared in `Proto`, not `Client`, so reading the interface back fails:
```
Client.swiftinterface:12:60: error: 'Element' is not imported through module 'Client'
Client.swiftinterface:1:1: error: failed to verify module interface of 'Client' due to the errors
above; the textual interface may be broken by project issues or a compiler bug
```
This is a regression. Swift 6.3.2 prints `Client.Bag<Element>.Element?`, which reads back fine.
### Reproduction
Two modules, eight lines, no dependencies, no package manifest.
```swift
// Proto.swift — declares the associated type
public protocol HasElement {
associatedtype Element
}
```
```swift
// Client.swift — conforms, and writes the return type as a member type path
import Proto
public struct Bag<Element>: HasElement {
public init() {}
public func firstAssoc() -> Self.Element? { nil }
}
```
```bash
swiftc -swift-version 5 -enable-library-evolution -module-name Proto \
-emit-module -emit-module-path Proto.swiftmodule \
-emit-module-interface-path Proto.swiftinterface Proto.swift
swiftc -swift-version 5 -enable-library-evolution -module-name Client -I . \
-emit-module -emit-module-path Client.swiftmodule \
-emit-module-interface-path Client.swiftinterface Client.swift
```
The second command fails in `verify-emitted-module-interface`. (On macOS, prefix both with `xcrun` so the host SDK is found.)
**Both source conditions are required:**
- The type must be written as a **member type path** (`Self.Element`). Writing the generic parameter directly (`-> Element?`) prints `Element?` and is fine.
- The associated type must be declared in a **different module**. Moving `HasElement` into `Client` makes the printer emit `Client::Bag<Element>.Client::Element?`, which is then correct and reads back fine.
Reproduces identically under `-swift-version 5` and `-swift-version 6`.
### `-enable-library-evolution` hides the bug rather than causing it
Dropping `-enable-library-evolution` makes the build **succeed**, but the emitted interface still contains the bad selector — verification is simply not run. That interface is still unreadable:
```console
$ grep firstAssoc Client.swiftinterface
public func firstAssoc() -> Client::Bag<Element>.Client::Element?
$ swift-frontend -typecheck-module-from-interface Client.swiftinterface -module-name Client -I .
Client.swiftinterface:12:60: error: 'Element' is not imported through module 'Client'
```
So the printer defect is unconditional; library evolution only turns on the check that catches it. Anyone emitting interfaces without verification ships a `.swiftinterface` that consumers cannot compile against.
### Expected behavior
The emitted interface should be readable by the compiler that produced it — either by attributing the associated type to its declaring module, or by omitting the selector in member type positions.
### Actual behavior
The interface fails to typecheck with `'Element' is not imported through module 'Client'`, both during `verify-emitted-module-interface` and on any later read-back.
### Real-world impact
This breaks library-evolution builds of [apple/swift-collections](https://github.com/apple/swift-collections). `OrderedSet.remove(at:)` is declared `-> Self.Element` (`OrderedSet+Partial RangeReplaceableCollection.swift:50`) and its `Element` comes from `Collection`/`SetAlgebra` in the `Swift` module, so it hits both conditions:
```swift
@inlinable public mutating func remove(at index: Swift::Int)
-> OrderedCollections::OrderedSet<Element>.OrderedCollections::Element {
```
Building any library with `BUILD_LIBRARY_FOR_DISTRIBUTION=YES` and `SWIFT_EMIT_MODULE_INTERFACE=YES` that depends on swift-collections `1.1.4` fails, with a cascade of overload-resolution errors in the `@inlinable` bodies the verifier re-parses (`extraneous argument label 'at:'`, `cannot convert value of type 'Int' to expected argument type 'Key'`).
Deleting only that one qualifier from the emitted interface — `.OrderedCollections::Element` to `.Element`, leaving the other 384 module selectors in the file untouched, including `Swift::Int` on the same line — makes the whole file verify clean, and the cascading body errors disappear with it.
Note the module itself compiles without error; only the interface is bad. That may explain how this escaped pre-release testing against swift-collections, since an ordinary build or test run never reads the interface back.
### Workaround
`-disable-module-selectors-in-module-interface` restores the 6.3.2 spelling. It is accepted by 6.3.2 and 6.4 alike, so it needs no version gating.
### Environment
| Toolchain | Printed type | Read-back |
| --- | --- | --- |
| Apple Swift 6.3.2 (Xcode 26.5) | `Client.Bag<Element>.Element?` | passes |
| Apple Swift 6.4 (`swiftlang-6.4.0.30.4`) | `Client::Bag<Element>.Client::Element?` | **fails** |
| Apple Swift 6.4 + `-disable-module-selectors-in-module-interface` | `Client.Bag<Element>.Element?` | passes |
Host: macOS 26, arm64. Only Apple toolchains were available to test; I have not checked an open-source swift.org toolchain or Linux, so I cannot say whether this also affects those builds.
1 条评论