Browse Source

[CodeGenLib] Add avialability gurads in generated code (#1791)

Motivation:

The protocols from GRPCCore that structs and protocols from the generated code conform to
can be used only for some OSes, so we need to add the same availability guards in the generated
code.

Modifications:

- created the AvailabilityDescription struct in StructuredSwiftRepresentation
- created the declaraion for a guarded declaration
- created the rendering functions for guarded declarations and the availability guard
- added the guarded declaration in the translators where needed
- changed the tests accordingly

Result:

The generated code will contain the necessary availability guards.
Stefana-Ioana Dranca 1 year ago
parent
commit
0daf4a1710

+ 17 - 0
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 {

+ 43 - 0
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)

+ 9 - 6
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
+        )
       )
     )
   }

+ 8 - 2
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(

+ 9 - 0
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"),
+    ])
+  }
 }

+ 15 - 0
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,

+ 8 - 0
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
           

+ 2 - 0
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) {}
       }
 

+ 16 - 0
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<NamespaceA.ServiceA.Method.Unary.Input>) async throws -> ServerResponse.Stream<NamespaceA.ServiceA.Method.Unary.Output>
       }
       /// 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<NamespaceA.ServiceA.Method.InputStreaming.Input>) async throws -> ServerResponse.Stream<NamespaceA.ServiceA.Method.InputStreaming.Output>
       }
       /// 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<NamespaceA.ServiceA.Method.OutputStreaming.Input>) async throws -> ServerResponse.Stream<NamespaceA.ServiceA.Method.OutputStreaming.Output>
       }
       /// 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<NamespaceA.ServiceA.Method.BidirectionalStreaming.Input>) async throws -> ServerResponse.Stream<NamespaceA.ServiceA.Method.BidirectionalStreaming.Output>
       }
       /// 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<NamespaceA.ServiceA.Method.InputStreaming.Input>) async throws -> ServerResponse.Stream<NamespaceA.ServiceA.Method.InputStreaming.Output>
@@ -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<ServiceA.Method.MethodA.Input>) async throws -> ServerResponse.Stream<ServiceA.Method.MethodA.Output>
       }
       /// 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

+ 6 - 0
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<Helloworld.Greeter.Method.SayHello.Input>) async throws -> ServerResponse.Stream<Helloworld.Greeter.Method.SayHello.Output>
@@ -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<Helloworld.Greeter.Method.SayHello.Input>) async throws -> ServerResponse.Stream<Helloworld.Greeter.Method.SayHello.Output>
@@ -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