From 5c215b25188d0e2049cfcee61fa2984cbbe1af03 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Thu, 4 Jan 2024 16:27:21 -0500 Subject: [PATCH 1/2] Add a CompileTest for "multi module" generation. - Add basic infrastructure to have Compile Tests. - Add a specific MultiModule CompileTest. - This ensure that with `import public` is properly handle and the generated code compiles even when a needed type is coming from a public import. - So the any relevant code changes are more visible, check the the generated code so the diffs for an changes related to the test will be easily reviewed. - The SPM plugin doesn't support multi module generation (https://github.com/apple/swift-protobuf/issues/1450), which is also why the code needs to be checked in, otherwise, it might simplify the tests. Note: The whole concept of CompileTests doesn't have to be a set up as distinct Swift Packages, but if we put the required targets in the main Package.swift, all projects using swift-protobuf would end up having the "overhead" of those test only targets, so I've errored on the side of a distinct package to make sure there is no impact of these tests on the folks using swift-protobuf. --- .github/workflows/build.yml | 3 + .gitignore | 2 + CompileTests/MultiModule/Package.swift | 40 ++++++ CompileTests/MultiModule/README.md | 7 ++ .../imports_a_publicly.pb.swift | 98 +++++++++++++++ .../imports_imports_a_publicly.pb.swift | 99 +++++++++++++++ .../MultiModule/Sources/ModuleA/a.pb.swift | 117 ++++++++++++++++++ .../MultiModule/Tests/Test1/test1.swift | 31 +++++ .../Tests/Test1/uses_a_transitively.pb.swift | 99 +++++++++++++++ .../MultiModule/Tests/Test2/test2.swift | 31 +++++ .../Tests/Test2/uses_a_transitively2.pb.swift | 100 +++++++++++++++ Makefile | 45 ++++++- .../ImportsAPublicly/imports_a_publicly.proto | 6 + .../imports_imports_a_publicly.proto | 6 + .../MultiModule/Sources/ModuleA/a.proto | 9 ++ .../Tests/Test1/uses_a_transitively.proto | 6 + .../Tests/Test2/uses_a_transitively2.proto | 6 + .../MultiModule/module_mappings.pbascii | 12 ++ .../imports_a_publicly.pb.swift | 98 +++++++++++++++ .../imports_imports_a_publicly.pb.swift | 99 +++++++++++++++ .../MultiModule/Sources/ModuleA/a.pb.swift | 117 ++++++++++++++++++ .../Tests/Test1/uses_a_transitively.pb.swift | 99 +++++++++++++++ .../Tests/Test2/uses_a_transitively2.pb.swift | 100 +++++++++++++++ 23 files changed, 1229 insertions(+), 1 deletion(-) create mode 100644 CompileTests/MultiModule/Package.swift create mode 100644 CompileTests/MultiModule/README.md create mode 100644 CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift create mode 100644 CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift create mode 100644 CompileTests/MultiModule/Sources/ModuleA/a.pb.swift create mode 100644 CompileTests/MultiModule/Tests/Test1/test1.swift create mode 100644 CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift create mode 100644 CompileTests/MultiModule/Tests/Test2/test2.swift create mode 100644 CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift create mode 100644 Protos/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.proto create mode 100644 Protos/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto create mode 100644 Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto create mode 100644 Protos/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.proto create mode 100644 Protos/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.proto create mode 100644 Protos/CompileTests/MultiModule/module_mappings.pbascii create mode 100644 Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift create mode 100644 Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift create mode 100644 Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift create mode 100644 Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift create mode 100644 Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5eb529d8..fb3c62424 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -102,6 +102,9 @@ jobs: - name: Test SPM plugin working-directory: main run: make test-spm-plugin PROTOC=../protobuf/cmake_build/protoc + - name: Compilation Tests + working-directory: main + run: make compile-tests PROTOC=../protobuf/cmake_build/protoc sanitizer_testing: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ff388b930..941c0c823 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ xcbaselines /docs /build mined_words.txt +/CompileTests/MultiModule/.build +/CompileTests/MultiModule/.swiftpm /*DescriptorTestData.bin /Package.resolved diff --git a/CompileTests/MultiModule/Package.swift b/CompileTests/MultiModule/Package.swift new file mode 100644 index 000000000..4be7b44d2 --- /dev/null +++ b/CompileTests/MultiModule/Package.swift @@ -0,0 +1,40 @@ +// swift-tools-version: 5.6 + +import PackageDescription + +let package = Package( + name: "CompileTests", + dependencies: [ + .package(name: "swift-protobuf", path: "../..") + ], + targets: [ + .testTarget( + name: "Test1", + dependencies: ["ImportsAPublicly"] + ), + .testTarget( + name: "Test2", + dependencies: ["ImportsImportsAPublicly"] + ), + .target( + name: "ModuleA", + dependencies: [ + .product(name: "SwiftProtobuf", package: "swift-protobuf"), + ] + ), + .target( + name: "ImportsAPublicly", + dependencies: [ + .product(name: "SwiftProtobuf", package: "swift-protobuf"), + .target(name: "ModuleA"), + ] + ), + .target( + name: "ImportsImportsAPublicly", + dependencies: [ + .product(name: "SwiftProtobuf", package: "swift-protobuf"), + .target(name: "ImportsAPublicly"), + ] + ), + ] +) diff --git a/CompileTests/MultiModule/README.md b/CompileTests/MultiModule/README.md new file mode 100644 index 000000000..245e25c16 --- /dev/null +++ b/CompileTests/MultiModule/README.md @@ -0,0 +1,7 @@ +# CompileTests/MultiModule + +This is a test case that uses sources generated into multiple modules and +ensures the generated code compiles with the cross modules references. + +This can't use the SwiftPM Plugin as that currently doesn't have support for the +module mappings. diff --git a/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift b/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift new file mode 100644 index 000000000..556b2ed19 --- /dev/null +++ b/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift @@ -0,0 +1,98 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ImportsAPublicly/imports_a_publicly.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct ImportsAPublicly: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension ImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ImportsAPublicly" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 11: .same(proto: "a"), + 12: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 11: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 12: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 12) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ImportsAPublicly, rhs: ImportsAPublicly) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift b/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift new file mode 100644 index 000000000..8a345f5d0 --- /dev/null +++ b/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift @@ -0,0 +1,99 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct ImportsImportsAPublicly: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension ImportsImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ImportsImportsAPublicly" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 21: .same(proto: "a"), + 22: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 21: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 22: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 21) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 22) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ImportsImportsAPublicly, rhs: ImportsImportsAPublicly) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift b/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift new file mode 100644 index 000000000..e456acc57 --- /dev/null +++ b/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift @@ -0,0 +1,117 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ModuleA/a.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public enum E: SwiftProtobuf.Enum { + public typealias RawValue = Int + case unset // = 0 + case a // = 1 + case b // = 2 + + public init() { + self = .unset + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unset + case 1: self = .a + case 2: self = .b + default: return nil + } + } + + public var rawValue: Int { + switch self { + case .unset: return 0 + case .a: return 1 + case .b: return 2 + } + } + +} + +public struct A: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var e: E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _e: E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension E: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "E_UNSET"), + 1: .same(proto: "E_A"), + 2: .same(proto: "E_B"), + ] +} + +extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "A" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: A, rhs: A) -> Bool { + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/CompileTests/MultiModule/Tests/Test1/test1.swift b/CompileTests/MultiModule/Tests/Test1/test1.swift new file mode 100644 index 000000000..f186277bd --- /dev/null +++ b/CompileTests/MultiModule/Tests/Test1/test1.swift @@ -0,0 +1,31 @@ +import ImportsAPublicly +import ModuleA // Needed because `import public` doesn't help Swift + +import XCTest + +final class ExampleTests: XCTestCase { + func testA() { + let anA = A.with { $0.e = .a } + XCTAssertEqual(anA.e, .a) + } + + func testImportsAPublicly() { + let imports = ImportsAPublicly.with { $0.a.e = .a } + XCTAssertEqual(imports.a.e, .a) + } + + func testInterop() { + let anA = A.with { $0.e = .b } + let imports = ImportsAPublicly.with { + $0.a = anA + $0.e = .b + } + XCTAssertEqual(imports.a.e, imports.e) + let transtively = UsesATransitively.with { + $0.a = anA + $0.e = imports.e + } + XCTAssertEqual(transtively.a, anA) + XCTAssertEqual(transtively.e, imports.e) + } +} diff --git a/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift b/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift new file mode 100644 index 000000000..3bb4d6701 --- /dev/null +++ b/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift @@ -0,0 +1,99 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Tests/Test1/uses_a_transitively.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct UsesATransitively: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension UsesATransitively: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "UsesATransitively" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 101: .same(proto: "a"), + 102: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 101: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 102: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 101) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 102) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: UsesATransitively, rhs: UsesATransitively) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/CompileTests/MultiModule/Tests/Test2/test2.swift b/CompileTests/MultiModule/Tests/Test2/test2.swift new file mode 100644 index 000000000..e20ebd130 --- /dev/null +++ b/CompileTests/MultiModule/Tests/Test2/test2.swift @@ -0,0 +1,31 @@ +import ImportsImportsAPublicly +import ModuleA // Needed because `import public` doesn't help Swift + +import XCTest + +final class ExampleTests: XCTestCase { + func testA() { + let anA = A.with { $0.e = .a } + XCTAssertEqual(anA.e, .a) + } + + func testImportsImportsAPublicly() { + let imports = ImportsImportsAPublicly.with { $0.a.e = .a } + XCTAssertEqual(imports.a.e, .a) + } + + func testInterop() { + let anA = A.with { $0.e = .b } + let imports = ImportsImportsAPublicly.with { + $0.a = anA + $0.e = .b + } + XCTAssertEqual(imports.a.e, imports.e) + let transtively = UsesATransitively2.with { + $0.a = anA + $0.e = imports.e + } + XCTAssertEqual(transtively.a, anA) + XCTAssertEqual(transtively.e, imports.e) + } +} diff --git a/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift b/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift new file mode 100644 index 000000000..7dd0c210e --- /dev/null +++ b/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift @@ -0,0 +1,100 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Tests/Test2/uses_a_transitively2.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ImportsImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct UsesATransitively2: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension UsesATransitively2: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "UsesATransitively2" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 111: .same(proto: "a"), + 122: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 111: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 122: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 111) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 122) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: UsesATransitively2, rhs: UsesATransitively2) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Makefile b/Makefile index 8c7eee166..51c14fa7e 100644 --- a/Makefile +++ b/Makefile @@ -77,12 +77,16 @@ PROTOS_DIRS=Conformance protoc-gen-swiftTests SwiftProtobuf SwiftProtobufPluginL check-proto-files \ check-version-numbers \ clean \ + compile-tests \ + compile-tests-multimodule \ default \ docs \ install \ pod-lib-lint \ reference \ regenerate \ + regenerate-compiletests-multimodule-protos \ + regenerate-compiletests-protos \ regenerate-conformance-protos \ regenerate-fuzz-protos \ regenerate-library-protos \ @@ -93,6 +97,7 @@ PROTOS_DIRS=Conformance protoc-gen-swiftTests SwiftProtobuf SwiftProtobufPluginL test-everything \ test-plugin \ test-runtime \ + test-spm-plugin \ update-proto-files default: build @@ -182,11 +187,26 @@ test-plugin: build ${PROTOC_GEN_SWIFT} --tfiws_out=_test/$$d \ `find Protos/$$d -type f -name "*.proto"` || exit 1; \ done + @mkdir -p _test/CompileTests/MultiModule + ${GENERATE_SRCS} \ + -I Protos/CompileTests/MultiModule \ + --tfiws_opt=Visibility=Public \ + --tfiws_opt=ProtoPathModuleMappings=Protos/CompileTests/MultiModule/module_mappings.pbascii \ + --tfiws_out=_test/CompileTests/MultiModule \ + `(find Protos/CompileTests/MultiModule -type f -name "*.proto")` diff -ru _test Reference # Test the SPM plugin. test-spm-plugin: - env PROTOC_PATH=$(shell realpath ${PROTOC}) swift test --package-path PluginExamples + env PROTOC_PATH=$(shell realpath ${PROTOC}) ${SWIFT} test --package-path PluginExamples + +compile-tests: compile-tests-multimodule + +# Test that ensure generating public into multiple modules with `import public` +# yields buildable code. +compile-tests-multimodule: + ${SWIFT} test --package-path CompileTests/MultiModule + # Rebuild the reference files by running the local version of protoc-gen-swift # against our menagerie of sample protos. @@ -213,6 +233,13 @@ reference: build ${PROTOC_GEN_SWIFT} --tfiws_out=Reference/$$d \ `find Protos/$$d -type f -name "*.proto"` || exit 1; \ done + @mkdir -p Reference/CompileTests/MultiModule + ${GENERATE_SRCS} \ + -I Protos/CompileTests/MultiModule \ + --tfiws_opt=Visibility=Public \ + --tfiws_opt=ProtoPathModuleMappings=Protos/CompileTests/MultiModule/module_mappings.pbascii \ + --tfiws_out=Reference/CompileTests/MultiModule \ + `(find Protos/CompileTests/MultiModule -type f -name "*.proto")` # # Rebuild the generated .pb.swift test files by running @@ -228,6 +255,7 @@ regenerate: \ regenerate-fuzz-protos \ regenerate-plugin-protos \ regenerate-test-protos \ + regenerate-compiletests-protos \ regenerate-conformance-protos \ Tests/protoc-gen-swiftTests/DescriptorTestData.swift \ Tests/SwiftProtobufPluginLibraryTests/DescriptorTestData.swift @@ -419,6 +447,21 @@ regenerate-conformance-protos: build ${PROTOC_GEN_SWIFT} --tfiws_out=Sources/Conformance \ `find Protos/Conformance -type f -name "*.proto"` +# Rebuild just the protos used by the CompileTests. +regenerate-compiletests-protos: regenerate-compiletests-multimodule-protos + +# Update the CompileTests/MultiModule files. +# NOTE: Any changes here must be done of the "test-plugin" target so it +# generates in the same way. +regenerate-compiletests-multimodule-protos: build ${PROTOC_GEN_SWIFT} + find CompileTests/MultiModule -name "*.pb.swift" -exec rm -f {} \; + ${GENERATE_SRCS} \ + -I Protos/CompileTests/MultiModule \ + --tfiws_opt=Visibility=Public \ + --tfiws_opt=ProtoPathModuleMappings=Protos/CompileTests/MultiModule/module_mappings.pbascii \ + --tfiws_out=CompileTests/MultiModule \ + `(find Protos/CompileTests/MultiModule -type f -name "*.proto")` + # Helper to check if there is a protobuf checkout as expected. check-for-protobuf-checkout: @if [ ! -d "${GOOGLE_PROTOBUF_CHECKOUT}/src/google/protobuf" ]; then \ diff --git a/Protos/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.proto b/Protos/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.proto new file mode 100644 index 000000000..f8bdb3722 --- /dev/null +++ b/Protos/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.proto @@ -0,0 +1,6 @@ +import public "Sources/ModuleA/a.proto"; + +message ImportsAPublicly { + optional A a = 11; + optional E e = 12; +} diff --git a/Protos/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto b/Protos/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto new file mode 100644 index 000000000..0b34fc68d --- /dev/null +++ b/Protos/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto @@ -0,0 +1,6 @@ +import public "Sources/ImportsAPublicly/imports_a_publicly.proto"; + +message ImportsImportsAPublicly { + optional A a = 21; + optional E e = 22; +} diff --git a/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto b/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto new file mode 100644 index 000000000..7a2ef4496 --- /dev/null +++ b/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto @@ -0,0 +1,9 @@ +enum E { + E_UNSET = 0; + E_A = 1; + E_B = 2; +} + +message A { + optional E e = 1; +} diff --git a/Protos/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.proto b/Protos/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.proto new file mode 100644 index 000000000..f8c2c3a80 --- /dev/null +++ b/Protos/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.proto @@ -0,0 +1,6 @@ +import "Sources/ImportsAPublicly/imports_a_publicly.proto"; + +message UsesATransitively { + optional A a = 101; + optional E e = 102; +} diff --git a/Protos/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.proto b/Protos/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.proto new file mode 100644 index 000000000..e05621dfa --- /dev/null +++ b/Protos/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.proto @@ -0,0 +1,6 @@ +import "Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto"; + +message UsesATransitively2 { + optional A a = 111; + optional E e = 122; +} diff --git a/Protos/CompileTests/MultiModule/module_mappings.pbascii b/Protos/CompileTests/MultiModule/module_mappings.pbascii new file mode 100644 index 000000000..656198148 --- /dev/null +++ b/Protos/CompileTests/MultiModule/module_mappings.pbascii @@ -0,0 +1,12 @@ +mapping { + module_name: "ModuleA" + proto_file_path: "Sources/ModuleA/a.proto" +} +mapping { + module_name: "ImportsAPublicly" + proto_file_path: "Sources/ImportsAPublicly/imports_a_publicly.proto" +} +mapping { + module_name: "ImportsImportsAPublicly" + proto_file_path: "Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto" +} diff --git a/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift b/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift new file mode 100644 index 000000000..556b2ed19 --- /dev/null +++ b/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift @@ -0,0 +1,98 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ImportsAPublicly/imports_a_publicly.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct ImportsAPublicly: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension ImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ImportsAPublicly" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 11: .same(proto: "a"), + 12: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 11: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 12: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 12) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ImportsAPublicly, rhs: ImportsAPublicly) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift b/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift new file mode 100644 index 000000000..8a345f5d0 --- /dev/null +++ b/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift @@ -0,0 +1,99 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ImportsImportsAPublicly/imports_imports_a_publicly.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct ImportsImportsAPublicly: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension ImportsImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ImportsImportsAPublicly" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 21: .same(proto: "a"), + 22: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 21: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 22: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 21) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 22) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ImportsImportsAPublicly, rhs: ImportsImportsAPublicly) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift b/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift new file mode 100644 index 000000000..e456acc57 --- /dev/null +++ b/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift @@ -0,0 +1,117 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Sources/ModuleA/a.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public enum E: SwiftProtobuf.Enum { + public typealias RawValue = Int + case unset // = 0 + case a // = 1 + case b // = 2 + + public init() { + self = .unset + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .unset + case 1: self = .a + case 2: self = .b + default: return nil + } + } + + public var rawValue: Int { + switch self { + case .unset: return 0 + case .a: return 1 + case .b: return 2 + } + } + +} + +public struct A: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var e: E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _e: E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension E: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "E_UNSET"), + 1: .same(proto: "E_A"), + 2: .same(proto: "E_B"), + ] +} + +extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "A" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: A, rhs: A) -> Bool { + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift b/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift new file mode 100644 index 000000000..3bb4d6701 --- /dev/null +++ b/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift @@ -0,0 +1,99 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Tests/Test1/uses_a_transitively.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct UsesATransitively: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension UsesATransitively: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "UsesATransitively" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 101: .same(proto: "a"), + 102: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 101: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 102: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 101) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 102) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: UsesATransitively, rhs: UsesATransitively) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift b/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift new file mode 100644 index 000000000..7dd0c210e --- /dev/null +++ b/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift @@ -0,0 +1,100 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: Tests/Test2/uses_a_transitively2.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +import Foundation +import SwiftProtobuf + +import ImportsAPublicly +import ImportsImportsAPublicly +import ModuleA + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _3: SwiftProtobuf.ProtobufAPIVersion_3 {} + typealias Version = _3 +} + +public struct UsesATransitively2: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var a: ModuleA.A { + get {return _a ?? ModuleA.A()} + set {_a = newValue} + } + /// Returns true if `a` has been explicitly set. + public var hasA: Bool {return self._a != nil} + /// Clears the value of `a`. Subsequent reads from it will return its default value. + public mutating func clearA() {self._a = nil} + + public var e: ModuleA.E { + get {return _e ?? .unset} + set {_e = newValue} + } + /// Returns true if `e` has been explicitly set. + public var hasE: Bool {return self._e != nil} + /// Clears the value of `e`. Subsequent reads from it will return its default value. + public mutating func clearE() {self._e = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _a: ModuleA.A? = nil + fileprivate var _e: ModuleA.E? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension UsesATransitively2: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "UsesATransitively2" + public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 111: .same(proto: "a"), + 122: .same(proto: "e"), + ] + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 111: try { try decoder.decodeSingularMessageField(value: &self._a) }() + case 122: try { try decoder.decodeSingularEnumField(value: &self._e) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._a { + try visitor.visitSingularMessageField(value: v, fieldNumber: 111) + } }() + try { if let v = self._e { + try visitor.visitSingularEnumField(value: v, fieldNumber: 122) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: UsesATransitively2, rhs: UsesATransitively2) -> Bool { + if lhs._a != rhs._a {return false} + if lhs._e != rhs._e {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} From b27d0190dc683716ba1f9cd967a8cc3f705c2a64 Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Mon, 8 Jan 2024 14:10:51 -0500 Subject: [PATCH 2/2] Revise how modules imports are done to better support `import public` Historically Swift didn't have a good way to model `import public` in .proto files. But by using `@_exported import`, we can get the majority of the way there. This makes restructuring of proto files less likely to break Swift source code; meaning Swift is less of a "special case" compared to other languages. If generating with `public` or `package` visibility, then instead of importing the whole module of for an `import public`, explicitly re-export the imports of the types from that file. That will make them usable for the given source file but will also let the types continue to be used by someone just importing this module. This also means a file with no `import public` usages becomes must simpler as it can just rely on its direct dependencies to provide all the types that could be used. --- .../imports_a_publicly.pb.swift | 10 +- .../imports_imports_a_publicly.pb.swift | 12 +- .../MultiModule/Sources/ModuleA/a.pb.swift | 59 +++- .../MultiModule/Tests/Test1/test1.swift | 2 +- .../Tests/Test1/uses_a_transitively.pb.swift | 6 +- .../MultiModule/Tests/Test2/test2.swift | 2 +- .../Tests/Test2/uses_a_transitively2.pb.swift | 7 +- .../MultiModule/Sources/ModuleA/a.proto | 5 + .../imports_a_publicly.pb.swift | 10 +- .../imports_imports_a_publicly.pb.swift | 12 +- .../MultiModule/Sources/ModuleA/a.pb.swift | 59 +++- .../Tests/Test1/uses_a_transitively.pb.swift | 6 +- .../Tests/Test2/uses_a_transitively2.pb.swift | 7 +- .../ProtoFileToModuleMappings.swift | 8 + .../SwiftProtobufNamer.swift | 4 +- .../Descriptor+TestHelpers.swift | 9 +- .../Descriptor+Extensions.swift | 144 ++++++++++ Sources/protoc-gen-swift/FileGenerator.swift | 23 +- .../Test_DescriptorExtensions.swift | 269 ++++++++++++++++++ 19 files changed, 618 insertions(+), 36 deletions(-) diff --git a/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift b/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift index 556b2ed19..99f4f3e88 100644 --- a/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift +++ b/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift @@ -10,7 +10,10 @@ import Foundation import SwiftProtobuf -import ModuleA +// Use of 'import public' causes re-exports: +@_exported import enum ModuleA.E +@_exported import let ModuleA.Extensions_ext_str +@_exported import struct ModuleA.A // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -62,6 +65,11 @@ extension ImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme 12: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift b/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift index 8a345f5d0..7e1ec8739 100644 --- a/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift +++ b/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift @@ -10,8 +10,11 @@ import Foundation import SwiftProtobuf -import ImportsAPublicly -import ModuleA +// Use of 'import public' causes re-exports: +@_exported import enum ModuleA.E +@_exported import let ModuleA.Extensions_ext_str +@_exported import struct ImportsAPublicly.ImportsAPublicly +@_exported import struct ModuleA.A // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -63,6 +66,11 @@ extension ImportsImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._Message 22: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift b/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift index e456acc57..165a10475 100644 --- a/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift +++ b/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift @@ -49,7 +49,7 @@ public enum E: SwiftProtobuf.Enum { } -public struct A: Sendable { +public struct A: SwiftProtobuf.ExtensibleMessage, Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -67,9 +67,57 @@ public struct A: Sendable { public init() {} + public var _protobuf_extensionFieldValues = SwiftProtobuf.ExtensionFieldValueSet() fileprivate var _e: E? = nil } +// MARK: - Extension support defined in a.proto. + +// MARK: - Extension Properties + +// Swift Extensions on the exteneded Messages to add easy access to the declared +// extension fields. The names are based on the extension field name from the proto +// declaration. To avoid naming collisions, the names are prefixed with the name of +// the scope where the extend directive occurs. + +extension A { + + public var extStr: String { + get {return getExtensionValue(ext: Extensions_ext_str) ?? String()} + set {setExtensionValue(ext: Extensions_ext_str, value: newValue)} + } + /// Returns true if extension `Extensions_ext_str` + /// has been explicitly set. + public var hasExtStr: Bool { + return hasExtensionValue(ext: Extensions_ext_str) + } + /// Clears the value of extension `Extensions_ext_str`. + /// Subsequent reads from it will return its default value. + public mutating func clearExtStr() { + clearExtensionValue(ext: Extensions_ext_str) + } + +} + +// MARK: - File's ExtensionMap: A_Extensions + +/// A `SwiftProtobuf.SimpleExtensionMap` that includes all of the extensions defined by +/// this .proto file. It can be used any place an `SwiftProtobuf.ExtensionMap` is needed +/// in parsing, or it can be combined with other `SwiftProtobuf.SimpleExtensionMap`s to create +/// a larger `SwiftProtobuf.SimpleExtensionMap`. +public let A_Extensions: SwiftProtobuf.SimpleExtensionMap = [ + Extensions_ext_str +] + +// Extension Objects - The only reason these might be needed is when manually +// constructing a `SimpleExtensionMap`, otherwise, use the above _Extension Properties_ +// accessors for the extension fields on the messages directly. + +public let Extensions_ext_str = SwiftProtobuf.MessageExtension, A>( + _protobuf_fieldNumber: 100, + fieldName: "ext_str" +) + // MARK: - Code below here is support for the SwiftProtobuf runtime. extension E: SwiftProtobuf._ProtoNameProviding { @@ -86,6 +134,11 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw 1: .same(proto: "e"), ] + public var isInitialized: Bool { + if !_protobuf_extensionFieldValues.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler @@ -93,6 +146,8 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._e) }() + case 100..<1001: + try { try decoder.decodeExtensionField(values: &_protobuf_extensionFieldValues, messageType: A.self, fieldNumber: fieldNumber) }() default: break } } @@ -106,12 +161,14 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw try { if let v = self._e { try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } }() + try visitor.visitExtensionFields(fields: _protobuf_extensionFieldValues, start: 100, end: 1001) try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: A, rhs: A) -> Bool { if lhs._e != rhs._e {return false} if lhs.unknownFields != rhs.unknownFields {return false} + if lhs._protobuf_extensionFieldValues != rhs._protobuf_extensionFieldValues {return false} return true } } diff --git a/CompileTests/MultiModule/Tests/Test1/test1.swift b/CompileTests/MultiModule/Tests/Test1/test1.swift index f186277bd..474a7c09c 100644 --- a/CompileTests/MultiModule/Tests/Test1/test1.swift +++ b/CompileTests/MultiModule/Tests/Test1/test1.swift @@ -1,5 +1,5 @@ import ImportsAPublicly -import ModuleA // Needed because `import public` doesn't help Swift +// Don't need to import ModuleA because of the file being a `import public` import XCTest diff --git a/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift b/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift index 3bb4d6701..e88624091 100644 --- a/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift +++ b/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift @@ -11,7 +11,6 @@ import Foundation import SwiftProtobuf import ImportsAPublicly -import ModuleA // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -63,6 +62,11 @@ extension UsesATransitively: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 102: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/CompileTests/MultiModule/Tests/Test2/test2.swift b/CompileTests/MultiModule/Tests/Test2/test2.swift index e20ebd130..45d62fbb5 100644 --- a/CompileTests/MultiModule/Tests/Test2/test2.swift +++ b/CompileTests/MultiModule/Tests/Test2/test2.swift @@ -1,5 +1,5 @@ import ImportsImportsAPublicly -import ModuleA // Needed because `import public` doesn't help Swift +// Don't need to import ModuleA because of the file being a `import public` import XCTest diff --git a/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift b/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift index 7dd0c210e..6c056cb8a 100644 --- a/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift +++ b/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift @@ -10,9 +10,7 @@ import Foundation import SwiftProtobuf -import ImportsAPublicly import ImportsImportsAPublicly -import ModuleA // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -64,6 +62,11 @@ extension UsesATransitively2: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 122: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto b/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto index 7a2ef4496..75c1506a7 100644 --- a/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto +++ b/Protos/CompileTests/MultiModule/Sources/ModuleA/a.proto @@ -6,4 +6,9 @@ enum E { message A { optional E e = 1; + extensions 100 to 1000; +} + +extend A { + optional string ext_str = 100; } diff --git a/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift b/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift index 556b2ed19..99f4f3e88 100644 --- a/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift +++ b/Reference/CompileTests/MultiModule/Sources/ImportsAPublicly/imports_a_publicly.pb.swift @@ -10,7 +10,10 @@ import Foundation import SwiftProtobuf -import ModuleA +// Use of 'import public' causes re-exports: +@_exported import enum ModuleA.E +@_exported import let ModuleA.Extensions_ext_str +@_exported import struct ModuleA.A // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -62,6 +65,11 @@ extension ImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme 12: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift b/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift index 8a345f5d0..7e1ec8739 100644 --- a/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift +++ b/Reference/CompileTests/MultiModule/Sources/ImportsImportsAPublicly/imports_imports_a_publicly.pb.swift @@ -10,8 +10,11 @@ import Foundation import SwiftProtobuf -import ImportsAPublicly -import ModuleA +// Use of 'import public' causes re-exports: +@_exported import enum ModuleA.E +@_exported import let ModuleA.Extensions_ext_str +@_exported import struct ImportsAPublicly.ImportsAPublicly +@_exported import struct ModuleA.A // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -63,6 +66,11 @@ extension ImportsImportsAPublicly: SwiftProtobuf.Message, SwiftProtobuf._Message 22: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift b/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift index e456acc57..165a10475 100644 --- a/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift +++ b/Reference/CompileTests/MultiModule/Sources/ModuleA/a.pb.swift @@ -49,7 +49,7 @@ public enum E: SwiftProtobuf.Enum { } -public struct A: Sendable { +public struct A: SwiftProtobuf.ExtensibleMessage, Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. @@ -67,9 +67,57 @@ public struct A: Sendable { public init() {} + public var _protobuf_extensionFieldValues = SwiftProtobuf.ExtensionFieldValueSet() fileprivate var _e: E? = nil } +// MARK: - Extension support defined in a.proto. + +// MARK: - Extension Properties + +// Swift Extensions on the exteneded Messages to add easy access to the declared +// extension fields. The names are based on the extension field name from the proto +// declaration. To avoid naming collisions, the names are prefixed with the name of +// the scope where the extend directive occurs. + +extension A { + + public var extStr: String { + get {return getExtensionValue(ext: Extensions_ext_str) ?? String()} + set {setExtensionValue(ext: Extensions_ext_str, value: newValue)} + } + /// Returns true if extension `Extensions_ext_str` + /// has been explicitly set. + public var hasExtStr: Bool { + return hasExtensionValue(ext: Extensions_ext_str) + } + /// Clears the value of extension `Extensions_ext_str`. + /// Subsequent reads from it will return its default value. + public mutating func clearExtStr() { + clearExtensionValue(ext: Extensions_ext_str) + } + +} + +// MARK: - File's ExtensionMap: A_Extensions + +/// A `SwiftProtobuf.SimpleExtensionMap` that includes all of the extensions defined by +/// this .proto file. It can be used any place an `SwiftProtobuf.ExtensionMap` is needed +/// in parsing, or it can be combined with other `SwiftProtobuf.SimpleExtensionMap`s to create +/// a larger `SwiftProtobuf.SimpleExtensionMap`. +public let A_Extensions: SwiftProtobuf.SimpleExtensionMap = [ + Extensions_ext_str +] + +// Extension Objects - The only reason these might be needed is when manually +// constructing a `SimpleExtensionMap`, otherwise, use the above _Extension Properties_ +// accessors for the extension fields on the messages directly. + +public let Extensions_ext_str = SwiftProtobuf.MessageExtension, A>( + _protobuf_fieldNumber: 100, + fieldName: "ext_str" +) + // MARK: - Code below here is support for the SwiftProtobuf runtime. extension E: SwiftProtobuf._ProtoNameProviding { @@ -86,6 +134,11 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw 1: .same(proto: "e"), ] + public var isInitialized: Bool { + if !_protobuf_extensionFieldValues.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler @@ -93,6 +146,8 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._e) }() + case 100..<1001: + try { try decoder.decodeExtensionField(values: &_protobuf_extensionFieldValues, messageType: A.self, fieldNumber: fieldNumber) }() default: break } } @@ -106,12 +161,14 @@ extension A: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, Sw try { if let v = self._e { try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } }() + try visitor.visitExtensionFields(fields: _protobuf_extensionFieldValues, start: 100, end: 1001) try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: A, rhs: A) -> Bool { if lhs._e != rhs._e {return false} if lhs.unknownFields != rhs.unknownFields {return false} + if lhs._protobuf_extensionFieldValues != rhs._protobuf_extensionFieldValues {return false} return true } } diff --git a/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift b/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift index 3bb4d6701..e88624091 100644 --- a/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift +++ b/Reference/CompileTests/MultiModule/Tests/Test1/uses_a_transitively.pb.swift @@ -11,7 +11,6 @@ import Foundation import SwiftProtobuf import ImportsAPublicly -import ModuleA // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -63,6 +62,11 @@ extension UsesATransitively: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem 102: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift b/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift index 7dd0c210e..6c056cb8a 100644 --- a/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift +++ b/Reference/CompileTests/MultiModule/Tests/Test2/uses_a_transitively2.pb.swift @@ -10,9 +10,7 @@ import Foundation import SwiftProtobuf -import ImportsAPublicly import ImportsImportsAPublicly -import ModuleA // If the compiler emits an error on this type, it is because this file // was generated by a version of the `protoc` Swift plug-in that is @@ -64,6 +62,11 @@ extension UsesATransitively2: SwiftProtobuf.Message, SwiftProtobuf._MessageImple 122: .same(proto: "e"), ] + public var isInitialized: Bool { + if let v = self._a, !v.isInitialized {return false} + return true + } + public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { // The use of inline closures is to circumvent an issue where the compiler diff --git a/Sources/SwiftProtobufPluginLibrary/ProtoFileToModuleMappings.swift b/Sources/SwiftProtobufPluginLibrary/ProtoFileToModuleMappings.swift index df21faf33..5b8549521 100644 --- a/Sources/SwiftProtobufPluginLibrary/ProtoFileToModuleMappings.swift +++ b/Sources/SwiftProtobufPluginLibrary/ProtoFileToModuleMappings.swift @@ -132,6 +132,14 @@ public struct ProtoFileToModuleMappings { } } + // NOTE: This api is only used by gRPC (or things like it), with + // `import public` now re-exporting things, this likely can go away or just + // be reduced just the above loop, without the need for special casing the + // `import public` cases. It will come down to what should expectations + // be for protobuf messages, enums, and extensions with repsect to something + // that generates on top if it. i.e. - should they re-export things or + // should only the generated proto code do it? + // Protocol Buffers has the concept of "public imports", these are imports // into a file that expose everything from within the file to the new // context. From the docs - diff --git a/Sources/SwiftProtobufPluginLibrary/SwiftProtobufNamer.swift b/Sources/SwiftProtobufPluginLibrary/SwiftProtobufNamer.swift index 83942d7b2..0dab8324c 100644 --- a/Sources/SwiftProtobufPluginLibrary/SwiftProtobufNamer.swift +++ b/Sources/SwiftProtobufPluginLibrary/SwiftProtobufNamer.swift @@ -17,8 +17,8 @@ import Foundation public final class SwiftProtobufNamer { var filePrefixCache = [String:String]() var enumValueRelativeNameCache = [String:String]() - var mappings: ProtoFileToModuleMappings - var targetModule: String + public let mappings: ProtoFileToModuleMappings + public let targetModule: String public var swiftProtobufModuleName: String { return mappings.swiftProtobufModuleName } diff --git a/Sources/SwiftProtobufTestHelpers/Descriptor+TestHelpers.swift b/Sources/SwiftProtobufTestHelpers/Descriptor+TestHelpers.swift index 263e988db..817b853d4 100644 --- a/Sources/SwiftProtobufTestHelpers/Descriptor+TestHelpers.swift +++ b/Sources/SwiftProtobufTestHelpers/Descriptor+TestHelpers.swift @@ -11,12 +11,13 @@ import SwiftProtobuf public extension Google_Protobuf_FileDescriptorProto { - init(name: String, dependencies: [String] = [], publicDependencies: [Int32] = []) { + init(name: String, dependencies: [String] = [], publicDependencies: [Int32] = [], package: String = "") { for idx in publicDependencies { precondition(Int(idx) <= dependencies.count) } self.init() self.name = name - dependency = dependencies - publicDependency = publicDependencies + self.dependency = dependencies + self.publicDependency = publicDependencies + self.package = package } init(textFormatStrings: [String]) throws { let s = textFormatStrings.joined(separator: "\n") + "\n" @@ -27,7 +28,7 @@ public extension Google_Protobuf_FileDescriptorProto { public extension Google_Protobuf_FileDescriptorSet { init(files: [Google_Protobuf_FileDescriptorProto]) { self.init() - file = files + self.file = files } init(file: Google_Protobuf_FileDescriptorProto) { self.init() diff --git a/Sources/protoc-gen-swift/Descriptor+Extensions.swift b/Sources/protoc-gen-swift/Descriptor+Extensions.swift index 0cb60effe..0aaa00e67 100644 --- a/Sources/protoc-gen-swift/Descriptor+Extensions.swift +++ b/Sources/protoc-gen-swift/Descriptor+Extensions.swift @@ -14,6 +14,150 @@ extension FileDescriptor { var isBundledProto: Bool { return SwiftProtobufInfo.isBundledProto(file: self) } + + // Returns a string of any import lines for the give file based on the file's + // imports. The string may include multiple lines. + // + // Protocol Buffers has the concept of "public imports", these are imports + // into a file that expose everything from within the file to the new + // context. From the docs - + // https://protobuf.dev/programming-guides/proto/#importing + // `import public` dependencies can be transitively relied upon by anyone + // importing the proto containing the import public statement. + // To properly expose the types for use, it means in each file, the public + // imports from the dependencies (recursively) have to be hoisted and + // reexported. This way someone importing a given module still sees the type + // even when moved. + // + // NOTE: There is a weakness for Swift with protobuf extensions. To make + // the protobuf extensions easier to use, a Swift extension is declared with + // field exposed as a property on the exteneded message. There is no way + // to reexport the Swift `extension` and/or added properties. But the raw + // types are re-exported to minimize the breaking of code if a type is moved + // between files/modules. + // + // `reexportPublicImports` will cause the `import public` types to be + // reexported to avoid breaking downstream code using a type that might have + // moved between .proto files. + // + // `asImplementationOnly` will cause all of the import directives to be + // marked as `@_implementationOnly`. It will also cause all of the `file`'s + // `publicDependencies` to instead be recursively pulled up as direct imports + // to ensure the generate file compiles, and no `import public` files are + // re-exported. + // + // Aside: This could be moved into the plugin library, but it doesn't seem + // like anyone else would need the logic. Swift GRPC support probably stick + // with the support for the module mappings. + public func computeImports( + namer: SwiftProtobufNamer, + reexportPublicImports: Bool, + asImplementationOnly: Bool + ) -> String { + // The namer should be configured with the module this file generated for. + assert(namer.targetModule == (namer.mappings.moduleName(forFile: self) ?? "")) + // Both options can't be enabled. + assert(!reexportPublicImports || + !asImplementationOnly || + reexportPublicImports != asImplementationOnly) + + guard namer.mappings.hasMappings else { + // No module mappings? Everything must be the same module, so no Swift + // imports will be needed. + return "" + } + + if dependencies.isEmpty { + // No proto dependencies (imports), then no Swift imports will be needed. + return "" + } + + let directive = asImplementationOnly ? "@_implementationOnly import" : "import" + var imports = Set() + for dependency in dependencies { + if SwiftProtobufInfo.isBundledProto(file: dependency) { + continue // No import needed for the runtime, that's always added. + } + if reexportPublicImports && publicDependencies.contains(where: { $0 === dependency }) { + // When re-exporting, the `import public` types will be imported + // instead of importing the module. + continue + } + if let depModule = namer.mappings.moduleName(forFile: dependency), + depModule != namer.targetModule { + // Different module, import it. + imports.insert("\(directive) \(depModule)") + } + } + + // If not re-exporting imports, then there is nothing special needed for + // `import public` files, as any transitive `import public` directives + // would have already re-exported the types, so everything this file needs + // will be covered by the above imports. + let exportingImports: [String] = + reexportPublicImports ? computeSymbolReExports(namer: namer) : [String]() + + var result = imports.sorted().joined(separator: "\n") + if !exportingImports.isEmpty { + if !result.isEmpty { + result.append("\n") + } + result.append("// Use of 'import public' causes re-exports:\n") + result.append(exportingImports.sorted().joined(separator: "\n")) + } + return result + } + + // Internal helper to `computeImports(...)`. + private func computeSymbolReExports(namer: SwiftProtobufNamer) -> [String] { + var result = [String]() + + // To handle re-exporting, recuively walk all the `import public` files + // and make this module do a Swift exporting import of the specific + // symbols. That will keep any type that gets moved between .proto files + // still exposed from the same modules so as not to break developer + // authored code. + var toScan = publicDependencies + var visited = Set() + while let dependency = toScan.popLast() { + let dependencyName = dependency.name + if visited.contains(dependencyName) { continue } + visited.insert(dependencyName) + + if SwiftProtobufInfo.isBundledProto(file: dependency) { + continue // Bundlined file, nothing to do. + } + guard let depModule = namer.mappings.moduleName(forFile: dependency) else { + continue // No mapping, assume same module, nothing to do. + } + if depModule == namer.targetModule { + // Same module, nothing to do (that generated file will do any re-exports). + continue + } + + toScan.append(contentsOf: dependency.publicDependencies) + + // NOTE: This re-exports/imports from the module that defines the type. + // If Xcode/SwiftPM ever were to do some sort of "layering checks" to + // ensure there is a direct dependency on the thing being imported, this + // could be updated do the re-export/import from the middle step in + // chained imports. + + for m in dependency.messages { + result.append("@_exported import struct \(namer.fullName(message: m))") + } + for e in dependency.enums { + result.append("@_exported import enum \(namer.fullName(enum: e))") + } + // There is nothing we can do for the Swift extensions declared on the + // extended Messages, best we can do is expose the raw extenions + // themselves. + for e in dependency.extensions { + result.append("@_exported import let \(namer.fullName(extensionField: e))") + } + } + return result + } } extension Descriptor { diff --git a/Sources/protoc-gen-swift/FileGenerator.swift b/Sources/protoc-gen-swift/FileGenerator.swift index e4055805a..18e46bbe3 100644 --- a/Sources/protoc-gen-swift/FileGenerator.swift +++ b/Sources/protoc-gen-swift/FileGenerator.swift @@ -90,25 +90,20 @@ class FileGenerator { p.print("\(comments)import Foundation") - // Import all other imports as @_implementationOnly if the option is - // set, to avoid exposing internal types to users. - let visibilityAnnotation: String - if self.generatorOptions.implementationOnlyImports { - precondition(self.generatorOptions.visibility == .internal) - visibilityAnnotation = "@_implementationOnly " - } else { - visibilityAnnotation = "" - } if fileDescriptor.isBundledProto { p.print("// 'import \(namer.swiftProtobufModuleName)' suppressed, this proto file is meant to be bundled in the runtime.") } else { - p.print("\(visibilityAnnotation)import \(namer.swiftProtobufModuleName)") + let directive = self.generatorOptions.implementationOnlyImports ? "@_implementationOnly import" : "import" + p.print("\(directive) \(namer.swiftProtobufModuleName)") } - if let neededImports = generatorOptions.protoToModuleMappings.neededModules(forFile: fileDescriptor) { + + let neededImports = fileDescriptor.computeImports( + namer: namer, + reexportPublicImports: self.generatorOptions.visibility != .internal, + asImplementationOnly: self.generatorOptions.implementationOnlyImports) + if !neededImports.isEmpty { p.print() - for i in neededImports { - p.print("\(visibilityAnnotation)import \(i)") - } + p.print(neededImports) } p.print() diff --git a/Tests/protoc-gen-swiftTests/Test_DescriptorExtensions.swift b/Tests/protoc-gen-swiftTests/Test_DescriptorExtensions.swift index 712070cb1..1c2a342eb 100644 --- a/Tests/protoc-gen-swiftTests/Test_DescriptorExtensions.swift +++ b/Tests/protoc-gen-swiftTests/Test_DescriptorExtensions.swift @@ -13,6 +13,8 @@ import SwiftProtobuf import SwiftProtobufPluginLibrary @testable import protoc_gen_swift +fileprivate typealias FileDescriptorProto = Google_Protobuf_FileDescriptorProto + final class Test_DescriptorExtensions: XCTestCase { func testExtensionRanges() throws { @@ -194,4 +196,271 @@ final class Test_DescriptorExtensions: XCTestCase { XCTAssertIdentical(aliasInfo.original(of: e.values[5]), e.values[0]) } + func test_File_computeImports_noImportPublic() { + let configText = """ + mapping { module_name: "foo", proto_file_path: "file" } + mapping { module_name: "bar", proto_file_path: "dir1/file" } + mapping { module_name: "baz", proto_file_path: ["dir2/file","file4"] } + mapping { module_name: "foo", proto_file_path: "file5" } + """ + + let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) + let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config) + + let fileProtos = [ + FileDescriptorProto(name: "file"), + FileDescriptorProto(name: "google/protobuf/any.proto", package: "google.protobuf"), + FileDescriptorProto(name: "dir1/file", dependencies: ["file"]), + FileDescriptorProto(name: "dir2/file", dependencies: ["google/protobuf/any.proto"]), + FileDescriptorProto(name: "file4", dependencies: ["dir2/file", "dir1/file", "file"]), + FileDescriptorProto(name: "file5", dependencies: ["file"]), + ] + let descSet = DescriptorSet(protos: fileProtos) + + // ( filename, imports, implOnly imports ) + let tests: [(String, String, String)] = [ + ( "file", "", "" ), + ( "dir1/file", "import foo", "@_implementationOnly import foo" ), + ( "dir2/file", "", "" ), + ( "file4", "import bar\nimport foo", "@_implementationOnly import bar\n@_implementationOnly import foo" ), + ( "file5", "", "" ), + ] + + for (name, expected, expectedImplOnly) in tests { + let fileDesc = descSet.files.filter{ $0.name == name }.first! + do { // reexportPublicImports: false, asImplementationOnly: false + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: false, asImplementationOnly: false) + XCTAssertEqual(result, expected, "Looking for \(name)") + } + do { // reexportPublicImports: true, asImplementationOnly: false - No `import publc`, same as previous + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: true, asImplementationOnly: false) + XCTAssertEqual(result, expected, "Looking for \(name)") + } + do { // reexportPublicImports: false, asImplementationOnly: true + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: false, asImplementationOnly: true) + XCTAssertEqual(result, expectedImplOnly, "Looking for \(name)") + } + } + } + + func test_File_computeImports_PublicImports() { + // See the notes on computeImports(namer:reexportPublicImports:asImplementationOnly:) + // about how public import complicate things. + + // Given: + // + // + File: a.proto + // message A {} + // + // enum E { + // E_UNSET = 0; + // E_A = 1; + // E_B = 2; + // } + // + // + File: imports_a_publicly.proto + // import public "a.proto"; + // + // message ImportsAPublicly { + // optional A a = 1; + // optional E e = 2; + // } + // + // + File: imports_imports_a_publicly.proto + // import public "imports_a_publicly.proto"; + // + // message ImportsImportsAPublicly { + // optional A a = 1; + // } + // + // + File: uses_a_transitively.proto + // import "imports_a_publicly.proto"; + // + // message UsesATransitively { + // optional A a = 1; + // } + // + // + File: uses_a_transitively2.proto + // import "imports_imports_a_publicly.proto"; + // + // message UsesATransitively2 { + // optional A a = 1; + // } + // + // With a mapping file of: + // + // mapping { + // module_name: "A" + // proto_file_path: "a.proto" + // } + // mapping { + // module_name: "ImportsAPublicly" + // proto_file_path: "imports_a_publicly.proto" + // } + // mapping { + // module_name: "ImportsImportsAPublicly" + // proto_file_path: "imports_imports_a_publicly.proto" + // } + + let configText = """ + mapping { module_name: "A", proto_file_path: "a.proto" } + mapping { module_name: "ImportsAPublicly", proto_file_path: "imports_a_publicly.proto" } + mapping { module_name: "ImportsImportsAPublicly", proto_file_path: "imports_imports_a_publicly.proto" } + """ + + let config = try! SwiftProtobuf_GenSwift_ModuleMappings(textFormatString: configText) + let mapper = try! ProtoFileToModuleMappings(moduleMappingsProto: config) + + let fileProtos = [ + try! FileDescriptorProto(textFormatString: """ + name: "a.proto" + message_type { + name: "A" + } + enum_type { + name: "E" + value { + name: "E_UNSET" + number: 0 + } + value { + name: "E_A" + number: 1 + } + value { + name: "E_B" + number: 2 + } + } + """), + try! FileDescriptorProto(textFormatString: """ + name: "imports_a_publicly.proto" + dependency: "a.proto" + message_type { + name: "ImportsAPublicly" + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".A" + json_name: "a" + } + field { + name: "e" + number: 2 + label: LABEL_OPTIONAL + type: TYPE_ENUM + type_name: ".E" + json_name: "e" + } + } + public_dependency: 0 + """), + try! FileDescriptorProto(textFormatString: """ + name: "imports_imports_a_publicly.proto" + dependency: "imports_a_publicly.proto" + message_type { + name: "ImportsImportsAPublicly" + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".A" + json_name: "a" + } + } + public_dependency: 0 + """), + try! FileDescriptorProto(textFormatString: """ + name: "uses_a_transitively.proto" + dependency: "imports_a_publicly.proto" + message_type { + name: "UsesATransitively" + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".A" + json_name: "a" + } + } + """), + try! FileDescriptorProto(textFormatString: """ + name: "uses_a_transitively2.proto" + dependency: "imports_imports_a_publicly.proto" + message_type { + name: "UsesATransitively2" + field { + name: "a" + number: 1 + label: LABEL_OPTIONAL + type: TYPE_MESSAGE + type_name: ".A" + json_name: "a" + } + } + """), + ] + let descSet = DescriptorSet(protos: fileProtos) + + // ( filename, imports, reExportPublicImports imports, implOnly imports ) + let tests: [(String, String, String, String)] = [ + ( "a.proto", + "", "", "" ), + ( "imports_a_publicly.proto", + "import A", + "// Use of 'import public' causes re-exports:\n@_exported import enum A.E\n@_exported import struct A.A", + "@_implementationOnly import A" ), + ( "imports_imports_a_publicly.proto", + "import ImportsAPublicly", + "// Use of 'import public' causes re-exports:\n@_exported import enum A.E\n@_exported import struct A.A\n@_exported import struct ImportsAPublicly.ImportsAPublicly", + "@_implementationOnly import ImportsAPublicly" ), + ( "uses_a_transitively.proto", + "import ImportsAPublicly", // this reexports A, so we don't directly pull in A. + "import ImportsAPublicly", // just a plain `import`, nothing to re-export. + "@_implementationOnly import ImportsAPublicly" ), + ( "uses_a_transitively2.proto", + "import ImportsImportsAPublicly", // this chain reexports A, so we don't directly pull in A. + "import ImportsImportsAPublicly", // just a plain `import`, nothing to re-export. + "@_implementationOnly import ImportsImportsAPublicly" ), + ] + + for (name, expected, expectedReExport, expectedImplOnly) in tests { + let fileDesc = descSet.files.filter{ $0.name == name }.first! + do { // reexportPublicImports: false, asImplementationOnly: false + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: false, asImplementationOnly: false) + XCTAssertEqual(result, expected, "Looking for \(name)") + } + do { // reexportPublicImports: true, asImplementationOnly: false + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: true, asImplementationOnly: false) + XCTAssertEqual(result, expectedReExport, "Looking for \(name)") + } + do { // reexportPublicImports: false, asImplementationOnly: true + let namer = + SwiftProtobufNamer(currentFile: fileDesc, + protoFileToModuleMappings: mapper) + let result = fileDesc.computeImports(namer: namer, reexportPublicImports: false, asImplementationOnly: true) + XCTAssertEqual(result, expectedImplOnly, "Looking for \(name)") + } + } + } + }