SE-0444 (Member Import Visibility) addresses the long-standing inconsistency where member declarations from transitively-imported modules are in scope in a source file even though top-level declarations from those same modules are not. The proposal fixes this by requiring that the defining module of any referenced member be directly imported. This behavior is available today behind the MemberImportVisibility upcoming feature flag and will become the default in a future language version.
After the initial implementation and adoption, library owners discovered a source-compatibility issue affecting the interaction between MemberImportVisibility and protocol conformance checking. This post proposes a targeted amendment to SE-0444 to address it.
The Problem
When checking whether a type satisfies a protocol's requirements, the compiler searches for witnesses of those requirements — either an explicit implementation in the conforming type, or a default implementation defined in a protocol extension. Under SE-0444, the compiler now applies MemberImportVisibility restrictions to the name lookups for these witnesses. This turns out to be source-breaking for a fundamental protocol evolution pattern, which is adding a new requirement to an existing protocol at the same time as providing a default implementation for that new requirement.
The issue surfaced concretely in the swift-foundation and swift-crypto packages. FoundationEssentials declares the ContiguousBytes protocol and, in a recent revision, added a withBytes requirement along with a default implementation:
// Module: FoundationEssentials
public protocol ContiguousBytes {
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R
func withBytes<R, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R // New requirement
}
extension ContiguousBytes {
// Default implementation of new requirement
public func withBytes<R, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { ... }
}
In swift-crypto, MD5Digest conforms to ContiguousBytes transitively through an internal protocol hierarchy. Crucially, the file declaring MD5Digest does not import FoundationEssentials. Instead, the relevant import lives in a separate file that defines the intermediate protocols:
// File: Digest.swift (imports FoundationEssentials)
import FoundationEssentials
public protocol Digest: ContiguousBytes { ... }
internal protocol DigestPrivate: Digest { ... }
// File: MD5.swift (does not import FoundationEssentials)
struct MD5Digest: DigestPrivate {
func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { ... }
// withBytes() is satisfied by the default implementation in FoundationEssentials
}
With MemberImportVisibility enabled, the compiler checks whether the default implementation of withBytes is visible in MD5.swift. Since FoundationEssentials is not directly imported there, the conformance is rejected:
error: type 'MD5Digest' does not conform to protocol 'ContiguousBytes'
note: instance method 'withBytes' used to satisfy a requirement of protocol 'ContiguousBytes'
is not available due to missing import of defining module 'FoundationEssentials'
swift-foundation had to revert the addition of the withBytes requirement from ContiguousBytes entirely to avoid breaking swift-crypto and potentially other clients. This is a significant regression: it should be possible to add protocol requirements with default implementations to a library without requiring source changes in clients, but with the current rules of MemberImportVisibility this cannot be guaranteed.
The goal of SE-0444 is to give developers explicit control over which modules' members are visible in a source file, preventing ambiguities and implicit dependencies from arising due to transitive imports. With that goal in mind, it generally makes sense to restrict protocol requirement witnesses to members that have been directly imported. This prevents conformances from implicitly creating dependencies on transitively imported modules. However, an exception should be made for the default implementations that are conceptually "inherited" by a conformance declaration.
Proposed Amendment
SE-0444 should include an explicit sub-section on conformances and default implementations:
Detailed design
...
Protocol Conformances
Types conforming to a protocol must have a witness for every requirement declared by that protocol. Under the rules of this proposal, a member that is considered to be a viable witness for a protocol requirement must be visible from the source file that declares the conformance. However, an exception must be made for a default implementation of a requirement so long as it is declared in a protocol extension in the same module as the extended protocol. This exception is necessary to give library owners a way to preserve source compatibility when adding new requirements to protocols.