Private protocols leaking members through protocol extensions

if we take as a given that the access level of a declaration may never be greater than the access level of its parent, then it follows that P.rawValue in the following example can never witness the public RawRepresentable.rawValue requirement of Struct, since Struct is public.

and yet, on Swift 6.2, this compiles. how can this be?

private protocol P: RawRepresentable<String> {
    init(name: String)
    var name: String { get }
}
extension P {
    public init(rawValue: String) {
        self.init(name: rawValue)
    }
    public var rawValue: String { self.name }
}

public struct Struct: P, RawRepresentable<String> {
    let name: String

    init(name: String) {
        self.name = name
    }
}

let s: Struct = .init(name: "Hello")
print(s.rawValue)

I copy-pasted your code into swift.godbolt.org and it did not compile under Swift 6.2

oops sorry, it looks i was using a 6.3 development snapshot by mistake, which permits PAT syntax in conformance declarations

public struct Struct: P, RawRepresentable<String> {
                                         ~~~~~~~~

but that’s unrelated to the underlying question, the PAT clause can be removed and it will still compile on 6.2.

Okay, the public struct picks up some public members from a protocol extension. The protocol itself happens to be private, but the extension members are still public.

Those public members satisfy the requirements of another public protocol. This is fine, because they are public members of a public type satisfying the requirements of a public protocol.

This code is written in the same file, so it is legal to use private init(name: "Hello") for internal let s.
In print(s.rawValue) the rawValue property of P is also used privately in file. If you access s.rawValue from another file than RawRepresentable.rawValue property is used.
Am I missing something?

Since public var rawValue is declared as public , it would be surprising if it were not visible or usable.

It can't witness for RawRepresentable.rawValue by itself, but since it's public, the compiler can synthesize RawRepresentable.rawValue as a trampoline to P.rawValue.

private protocol P { // notice the absence of 'RawRepresentable' here
  init(name: String)
  var name: String { get }
}

extension P {
  public init(rawValue: String) {
    self.init(name: rawValue)
  }
  public var rawValue: String { self.name }

  // none of these are here to satisfy something, they just exist
}

public struct Struct: P {
  let name: String

  init(name: String) {
    self.name = name
  }
}

extension Struct: RawRepresentable {
  // We need 'init?(rawValue:)' and 'var rawValue: Self.RawValue { get }'.
  // In this visibility scope, we see them implemented and accessible because
  //  'extension P' declares them as public.
  // So we can syntesize the witnesses with simple proxies.

  // public init?(rawValue: String) {
  //   self = Self.`extension P`::init(rawValue: rawValue)
  // }
  // public var rawValue: String { 
  //   self.`extension P`::rawValue
  // }
}

And in SIL you can see that:

  • the witness for RawRepresentable.init(rawValue:) calls P.init(rawValue:)
  • the witness for RawRepresentable.rawValue.getter calls P.rawValue.getter
sil [transparent] [thunk] [ossa] @protocol witness for Swift.RawRepresentable.init(rawValue: A.RawValue) -> A? in conformance output.Struct : Swift.RawRepresentable in output : $@convention(witness_method: RawRepresentable) (@in String, @thick Struct.Type) -> @out Optional<Struct> {
bb0(%0 : $*Optional<Struct>, %1 : $*String, %2 : $@thick Struct.Type):
  %3 = load [take] %1, loc * "/app/example.swift":9:10, scope 8 // user: %6

  // here
  %4 = function_ref @(extension in output):output.(P in _60494E8B9C642A7C4A26F3A3B6CECEB9).init(rawValue: Swift.String) -> A : $@convention(method) <τ_0_0 where τ_0_0 : P> (@owned String, @thick τ_0_0.Type) -> @out τ_0_0, loc * "/app/example.swift":9:10, scope 8 // user: %6

  %5 = alloc_stack $Struct, loc * "/app/example.swift":9:10, scope 8 // users: %11, %7, %6
  %6 = apply %4<Struct>(%5, %3, %2) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@owned String, @thick τ_0_0.Type) -> @out τ_0_0, loc * "/app/example.swift":9:10, scope 8
  %7 = load [take] %5, loc * "/app/example.swift":9:10, scope 8 // user: %8
  %8 = enum $Optional<Struct>, #Optional.some!enumelt, %7, loc * "/app/example.swift":9:10, scope 8 // user: %9
  store %8 to [init] %0, loc * "/app/example.swift":9:10, scope 8 // id: %9
  %10 = tuple (), loc * "/app/example.swift":9:10, scope 8 // user: %12
  dealloc_stack %5, loc * "/app/example.swift":11:3, scope 8 // id: %11
  return %10, loc * "/app/example.swift":9:10, scope 8 // id: %12
} // end sil function 'protocol witness for Swift.RawRepresentable.init(rawValue: A.RawValue) -> A? in conformance output.Struct : Swift.RawRepresentable in output'

sil [transparent] [thunk] [ossa] @protocol witness for Swift.RawRepresentable.rawValue.getter : A.RawValue in conformance output.Struct : Swift.RawRepresentable in output : $@convention(witness_method: RawRepresentable) (@in_guaranteed Struct) -> @out String {
bb0(%0 : $*String, %1 : $*Struct):

  // here
  %2 = function_ref @(extension in output):output.(P in _60494E8B9C642A7C4A26F3A3B6CECEB9).rawValue.getter : Swift.String : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> @owned String, loc * "/app/example.swift":12:31, scope 9 // user: %3

  %3 = apply %2<Struct>(%1) : $@convention(method) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> @owned String, loc * "/app/example.swift":12:31, scope 9 // user: %4
  store %3 to [init] %0, loc * "/app/example.swift":12:31, scope 9 // id: %4
  %5 = tuple (), loc * "/app/example.swift":12:31, scope 9 // user: %6
  return %5, loc * "/app/example.swift":12:31, scope 9 // id: %6
} // end sil function 'protocol witness for Swift.RawRepresentable.rawValue.getter : A.RawValue in conformance output.Struct : Swift.RawRepresentable in output'

(Compiler Explorer)

If they were fileprivate, you'd have to do that manually.