diff --git a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift index 2b701f34d..eb3abe149 100644 --- a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift +++ b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift @@ -818,6 +818,8 @@ struct TextBasedRenderer: RendererProtocol { renderCommentableDeclaration(comment: comment, declaration: nestedDeclaration) case let .deprecated(deprecation, nestedDeclaration): renderDeprecatedDeclaration(deprecation: deprecation, declaration: nestedDeclaration) + case let .guarded(availability, nestedDeclaration): + renderGuardedDeclaration(availability: availability, declaration: nestedDeclaration) case .variable(let variableDescription): renderVariable(variableDescription) case .extension(let extensionDescription): renderExtension(extensionDescription) case .struct(let structDescription): renderStruct(structDescription) @@ -1074,6 +1076,21 @@ struct TextBasedRenderer: RendererProtocol { writer.writeLine(line) } + /// Renders the specified declaration with an availability guard annotation. + func renderGuardedDeclaration(availability: AvailabilityDescription, declaration: Declaration) { + renderAvailability(availability) + renderDeclaration(declaration) + } + + func renderAvailability(_ availability: AvailabilityDescription) { + var line = "@available(" + for osVersion in availability.osVersions { + line.append("\(osVersion.os.name) \(osVersion.version), ") + } + line.append("*)") + writer.writeLine(line) + } + /// Renders the specified code block item. func renderCodeBlockItem(_ description: CodeBlockItem) { switch description { diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift index 7a91fcd94..e40b8eb5c 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift +++ b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift @@ -764,6 +764,9 @@ indirect enum Declaration: Equatable, Codable { /// A declaration that adds a comment on top of the provided declaration. case deprecated(DeprecationDescription, Declaration) + /// A declaration that adds an availability guard on top of the provided declaration. + case guarded(AvailabilityDescription, Declaration) + /// A variable declaration. case variable(VariableDescription) @@ -801,6 +804,42 @@ struct DeprecationDescription: Equatable, Codable { var renamed: String? } +/// A description of an availability guard. +/// +/// For example: `@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)` +struct AvailabilityDescription: Equatable, Codable { + /// The array of OSes and versions which are specified in the availability guard. + var osVersions: [OSVersion] + init(osVersions: [OSVersion]) { + self.osVersions = osVersions + } + + /// An OS and its version. + struct OSVersion: Equatable, Codable { + var os: OS + var version: String + init(os: OS, version: String) { + self.os = os + self.version = version + } + } + + /// One of the possible OSes. + // swift-format-ignore: DontRepeatTypeInStaticProperties + struct OS: Equatable, Codable { + var name: String + + init(name: String) { + self.name = name + } + + static let macOS = Self(name: "macOS") + static let iOS = Self(name: "iOS") + static let watchOS = Self(name: "watchOS") + static let tvOS = Self(name: "tvOS") + } +} + /// A description of an assignment expression. /// /// For example: `foo = 42`. @@ -1803,6 +1842,7 @@ extension Declaration { switch self { case .commentable(_, let declaration): return declaration.accessModifier case .deprecated(_, let declaration): return declaration.accessModifier + case .guarded(_, let declaration): return declaration.accessModifier case .variable(let variableDescription): return variableDescription.accessModifier case .extension(let extensionDescription): return extensionDescription.accessModifier case .struct(let structDescription): return structDescription.accessModifier @@ -1821,6 +1861,9 @@ extension Declaration { case .deprecated(let deprecationDescription, var declaration): declaration.accessModifier = newValue self = .deprecated(deprecationDescription, declaration) + case .guarded(let availability, var declaration): + declaration.accessModifier = newValue + self = .guarded(availability, declaration) case .variable(var variableDescription): variableDescription.accessModifier = newValue self = .variable(variableDescription) diff --git a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift index 924415b96..a669c8f41 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift @@ -332,12 +332,15 @@ extension ClientCodeTranslator { ) } - return .struct( - StructDescription( - accessModifier: self.accessModifier, - name: "\(service.namespacedGeneratedName)Client", - conformances: ["\(service.namespacedTypealiasGeneratedName).ClientProtocol"], - members: [clientProperty, initializer] + methods + return .guarded( + self.availabilityGuard, + .struct( + StructDescription( + accessModifier: self.accessModifier, + name: "\(service.namespacedGeneratedName)Client", + conformances: ["\(service.namespacedTypealiasGeneratedName).ClientProtocol"], + members: [clientProperty, initializer] + methods + ) ) ) } diff --git a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift index a20067ee3..eebfd59e1 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift @@ -125,7 +125,10 @@ extension ServerCodeTranslator { ) ) - return .commentable(.preFormatted(service.documentation), streamingProtocol) + return .commentable( + .preFormatted(service.documentation), + .guarded(self.availabilityGuard, streamingProtocol) + ) } private func makeStreamingMethodSignature( @@ -188,7 +191,10 @@ extension ServerCodeTranslator { ] ) let registerRPCsBody = self.makeRegisterRPCsMethodBody(for: service, in: codeGenerationRequest) - return .function(signature: registerRPCsSignature, body: registerRPCsBody) + return .guarded( + self.availabilityGuard, + .function(signature: registerRPCsSignature, body: registerRPCsBody) + ) } private func makeRegisterRPCsMethodBody( diff --git a/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift index ff73a9481..b73a75c95 100644 --- a/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift +++ b/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift @@ -46,4 +46,13 @@ extension SpecializedTranslator { } } } + + internal var availabilityGuard: AvailabilityDescription { + AvailabilityDescription(osVersions: [ + .init(os: .macOS, version: "13.0"), + .init(os: .iOS, version: "16.0"), + .init(os: .watchOS, version: "9.0"), + .init(os: .tvOS, version: "16.0"), + ]) + } } diff --git a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift b/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift index 5a56d12fa..422673e8b 100644 --- a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift @@ -667,6 +667,21 @@ final class Test_TextBasedRenderer: XCTestCase { ) } + func testAvailability() throws { + try _test( + .init(osVersions: [ + .init(os: .macOS, version: "12.0"), + .init(os: .iOS, version: "13.1.2"), + .init(os: .watchOS, version: "8.1.2"), + .init(os: .tvOS, version: "15.0.2"), + ]), + renderedBy: TextBasedRenderer.renderAvailability, + rendersAs: #""" + @available(macOS 12.0, iOS 13.1.2, watchOS 8.1.2, tvOS 15.0.2, *) + """# + ) + } + func testBindingKind() throws { try _test( .var, diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift index 670895e26..a68bdd5da 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift @@ -66,6 +66,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -139,6 +140,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -212,6 +214,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -285,6 +288,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -386,6 +390,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -475,6 +480,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { } } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal struct ServiceAClient: ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -535,6 +541,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { extension NamespaceA.ServiceA.ClientProtocol { } /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct NamespaceA_ServiceAClient: NamespaceA.ServiceA.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -551,6 +558,7 @@ final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { /// Documentation for ServiceB /// /// Line 2 + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public struct ServiceBClient: ServiceB.ClientProtocol { private let client: GRPCCore.GRPCClient diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift index e34a197b0..59016e642 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift @@ -181,10 +181,12 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { } /// Documentation for AService + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) {} } diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift index bbf8090bb..731595fcc 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift @@ -51,12 +51,14 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for unaryMethod func unary(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: NamespaceA.ServiceA.Method.Unary.descriptor, @@ -115,12 +117,14 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for inputStreamingMethod func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: NamespaceA.ServiceA.Method.InputStreaming.descriptor, @@ -183,12 +187,14 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for outputStreamingMethod func outputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: NamespaceA.ServiceA.Method.OutputStreaming.descriptor, @@ -251,12 +257,14 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for bidirectionalStreamingMethod func bidirectionalStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: NamespaceA.ServiceA.Method.BidirectionalStreaming.descriptor, @@ -327,6 +335,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for inputStreamingMethod func inputStreaming(request: ServerRequest.Stream) async throws -> ServerResponse.Stream @@ -336,6 +345,7 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { } /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: NamespaceA.ServiceA.Method.InputStreaming.descriptor, @@ -406,12 +416,14 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal protocol ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Documentation for MethodA func methodA(request: ServerRequest.Stream) async throws -> ServerResponse.Stream } /// Conformance to `GRPCCore.RegistrableRPCService`. extension ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: ServiceA.Method.MethodA.descriptor, @@ -468,9 +480,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { let expectedSwift = """ /// Documentation for ServiceA + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceA.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) {} } /// Documentation for ServiceA @@ -479,9 +493,11 @@ final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { extension NamespaceA.ServiceA.ServiceProtocol { } /// Documentation for ServiceB + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol NamespaceA_ServiceBStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} /// Conformance to `GRPCCore.RegistrableRPCService`. extension NamespaceA.ServiceB.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) {} } /// Documentation for ServiceB diff --git a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift b/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift index bcea02742..d6bb08059 100644 --- a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift +++ b/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift @@ -102,6 +102,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { } /// The greeting service definition. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) internal struct Helloworld_GreeterClient: Helloworld.Greeter.ClientProtocol { private let client: GRPCCore.GRPCClient @@ -181,6 +182,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { } /// The greeting service definition. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public protocol Helloworld_GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Sends a greeting. func sayHello(request: ServerRequest.Stream) async throws -> ServerResponse.Stream @@ -188,6 +190,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { /// Conformance to `GRPCCore.RegistrableRPCService`. extension Helloworld.Greeter.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) public func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: Helloworld.Greeter.Method.SayHello.descriptor, @@ -270,6 +273,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { } /// The greeting service definition. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package protocol Helloworld_GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService { /// Sends a greeting. func sayHello(request: ServerRequest.Stream) async throws -> ServerResponse.Stream @@ -277,6 +281,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { /// Conformance to `GRPCCore.RegistrableRPCService`. extension Helloworld.Greeter.StreamingServiceProtocol { + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package func registerMethods(with router: inout GRPCCore.RPCRouter) { router.registerHandler( forMethod: Helloworld.Greeter.Method.SayHello.descriptor, @@ -329,6 +334,7 @@ final class ProtobufCodeGeneratorTests: XCTestCase { } /// The greeting service definition. + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) package struct Helloworld_GreeterClient: Helloworld.Greeter.ClientProtocol { private let client: GRPCCore.GRPCClient