Browse Source

Added TypeName and TypeUsage from OpenAPI Generator. (#1726)

Motivation:

The types are used by the code renderer that will be brought over from OpenAPI Generator
to the new gRPC Swift CodeGenLib.

Modifications:

Copied the TypeName, TypeUsage and some TypeName extensions that will be used
in the context of gRPC, and modified TypeName so it represents only the Swift path
of a type.

Result:

We will be able to bring over the Code Renderer and we will have types representations that can be used
by the service translator.
Stefana-Ioana Dranca 2 years ago
parent
commit
bd0d0fdbce
3 changed files with 340 additions and 1 deletions
  1. 3 1
      NOTICES.txt
  2. 105 0
      Sources/GRPCCodeGen/Internal/TypeName.swift
  3. 232 0
      Sources/GRPCCodeGen/Internal/TypeUsage.swift

+ 3 - 1
NOTICES.txt

@@ -60,9 +60,11 @@ This product uses derivations of swift-extras/swift-extras-base64 'Base64.swift'
 
 ---
 
-This product uses derivations of apple/swift-openapi-generator 'StructuredSwiftRepresentation.swift'.
+This product uses derivations of apple/swift-openapi-generator 'StructuredSwiftRepresentation.swift',
+'TypeName.swift', 'TypeUsage.swift' and 'Builtins.swift'.
     
     * LICENSE (Apache License 2.0):
       * https://github.com/apple/swift-openapi-generator/blob/main/LICENSE.txt
     * HOMEPAGE:
       * https://github.com/apple/swift-openapi-generator
+

+ 105 - 0
Sources/GRPCCodeGen/Internal/TypeName.swift

@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftOpenAPIGenerator open source project
+//
+// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+import Foundation
+
+/// A fully-qualified type name that contains the components of the Swift
+/// type name.
+///
+/// Use the type name to define a type, see also `TypeUsage` when referring
+/// to a type.
+struct TypeName: Hashable {
+  /// A list of components that make up the type name.
+  private let components: [String]
+
+  /// Creates a new type name with the specified list of components.
+  /// - Parameter components: A list of components for the type.
+  init(components: [String]) {
+    precondition(!components.isEmpty, "TypeName path cannot be empty")
+    self.components = components
+  }
+
+  /// A string representation of the fully qualified Swift type name.
+  ///
+  /// For example: `Swift.Int`.
+  var fullyQualifiedName: String { components.joined(separator: ".") }
+
+  /// A string representation of the last path component of the Swift
+  /// type name.
+  ///
+  /// For example: `Int`.
+  var shortName: String { components.last! }
+
+  /// Returns a type name by appending the specified component to the
+  /// current type name.
+  ///
+  /// In other words, returns a type name for a child type.
+  /// - Parameters:
+  ///   - component: The name of the Swift type component.
+  /// - Returns: A new type name.
+  func appending(component: String) -> Self {
+    return .init(components: components + [component])
+  }
+
+  /// Returns a type name by removing the last component from the current
+  /// type name.
+  ///
+  /// In other words, returns a type name for the parent type.
+  var parent: TypeName {
+    precondition(components.count >= 1, "Cannot get the parent of a root type")
+    return .init(components: components.dropLast())
+  }
+}
+
+extension TypeName: CustomStringConvertible {
+  var description: String {
+    return fullyQualifiedSwiftName
+  }
+}
+
+extension TypeName {
+  /// Returns the type name for the String type.
+  static var string: Self { .swift("String") }
+
+  /// Returns the type name for the Int type.
+  static var int: Self { .swift("Int") }
+
+  /// Returns a type name for a type with the specified name in the
+  /// Swift module.
+  /// - Parameter name: The name of the type.
+  /// - Returns: A TypeName representing the specified type within the Swift module.
+  static func swift(_ name: String) -> TypeName { TypeName(components: ["Swift", name]) }
+
+  /// Returns a type name for a type with the specified name in the
+  /// Foundation module.
+  /// - Parameter name: The name of the type.
+  /// - Returns: A TypeName representing the specified type within the Foundation module.
+  static func foundation(_ name: String) -> TypeName {
+    TypeName(components: ["Foundation", name])
+  }
+}

+ 232 - 0
Sources/GRPCCodeGen/Internal/TypeUsage.swift

@@ -0,0 +1,232 @@
+/*
+ * Copyright 2023, gRPC Authors All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the SwiftOpenAPIGenerator open source project
+//
+// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+/// A reference to a Swift type, including modifiers such as whether the
+/// type is wrapped in an optional, an array, or a dictionary.
+///
+/// Whenever unsure whether to use `TypeUsage` or `TypeName` in a new API,
+/// consider whether you need to define a type or refer to a type.
+///
+/// To define a type, use `TypeName`, and to refer to a type, use `TypeUsage`.
+///
+/// This type is not meant to represent all the various ways types can be
+/// wrapped in Swift, only the ways we wrap things in this project. For example,
+/// double optionals (`String??`) are automatically collapsed into a single
+/// optional, and so on.
+struct TypeUsage {
+
+  /// Describes either a type name or a type usage.
+  fileprivate indirect enum Wrapped {
+
+    /// A type name, used to define a type.
+    case name(TypeName)
+
+    /// A type usage, used to refer to a type.
+    case usage(TypeUsage)
+  }
+
+  /// The underlying type.
+  fileprivate var wrapped: Wrapped
+
+  /// Describes the usage of the wrapped type.
+  fileprivate enum Usage {
+
+    /// An unchanged underlying type.
+    ///
+    /// For example: `Wrapped` stays `Wrapped`.
+    case identity
+
+    /// An optional wrapper for the underlying type.
+    ///
+    /// For example: `Wrapped` becomes `Wrapped?`.
+    case optional
+
+    /// An array wrapped for the underlying type.
+    ///
+    /// For example: `Wrapped` becomes `[Wrapped]`.
+    case array
+
+    /// A dictionary value wrapper for the underlying type.
+    ///
+    /// For example: `Wrapped` becomes `[String: Wrapped]`.
+    case dictionaryValue
+
+    /// A generic type wrapper for the underlying type.
+    ///
+    /// For example, `Wrapped` becomes `Wrapper<Wrapped>`.
+    case generic(wrapper: TypeName)
+  }
+
+  /// The type usage applied to the underlying type.
+  fileprivate var usage: Usage
+}
+
+extension TypeUsage: CustomStringConvertible { var description: String { fullyQualifiedName } }
+
+extension TypeUsage {
+
+  /// A Boolean value that indicates whether the type is optional.
+  var isOptional: Bool {
+    guard case .optional = usage else { return false }
+    return true
+  }
+
+  /// A string representation of the last component of the Swift type name.
+  ///
+  /// For example: `Int`.
+  var shortName: String {
+    let component: String
+    switch wrapped {
+    case let .name(typeName): component = typeName.shortName
+    case let .usage(usage): component = usage.shortName
+    }
+    return applied(to: component)
+  }
+
+  /// A string representation of the fully qualified type name.
+  ///
+  /// For example: `Swift.Int`.
+  var fullyQualifiedName: String {
+    let component: String
+    switch wrapped {
+    case let .name(typeName): component = typeName.fullyQualifiedName
+    case let .usage(usage): component = usage.fullyQualifiedName
+    }
+    return applied(to: component)
+  }
+
+  /// A string representation of the fully qualified Swift type name, with
+  /// any optional wrapping removed.
+  ///
+  /// For example: `Swift.Int`.
+  var fullyQualifiedNonOptionalName: String { withOptional(false).fullyQualifiedName }
+
+  /// Returns a string representation of the type usage applied to the
+  /// specified Swift path component.
+  /// - Parameter component: A Swift path component.
+  /// - Returns: A string representation of the specified Swift path component with the applied type usage.
+  private func applied(to component: String) -> String {
+    switch usage {
+    case .identity: return component
+    case .optional: return component + "?"
+    case .array: return "[" + component + "]"
+    case .dictionaryValue: return "[String: " + component + "]"
+    case .generic(wrapper: let wrapper):
+      return "\(wrapper.fullyQualifiedName)<" + component + ">"
+    }
+  }
+
+  /// The type name wrapped by the current type usage.
+  var typeName: TypeName {
+    switch wrapped {
+    case .name(let typeName): return typeName
+    case .usage(let typeUsage): return typeUsage.typeName
+    }
+  }
+
+  /// A type usage created by treating the current type usage as an optional
+  /// type.
+  var asOptional: Self {
+    // Don't double wrap optionals
+    guard !isOptional else { return self }
+    return TypeUsage(wrapped: .usage(self), usage: .optional)
+  }
+
+  /// A type usage created by removing the outer type usage wrapper.
+  private var unwrappedOneLevel: Self {
+    switch wrapped {
+    case let .usage(usage): return usage
+    case let .name(typeName): return typeName.asUsage
+    }
+  }
+
+  /// Returns a type usage created by adding or removing an optional wrapper,
+  /// controlled by the specified parameter.
+  /// - Parameter isOptional: If `true`, wraps the current type usage in
+  /// an optional. If `false`, removes a potential optional wrapper from the
+  /// top level.
+  /// - Returns: A type usage with the adjusted optionality based on the `isOptional` parameter.
+  func withOptional(_ isOptional: Bool) -> Self {
+    if (isOptional && self.isOptional) || (!isOptional && !self.isOptional) { return self }
+    guard isOptional else { return unwrappedOneLevel }
+    return asOptional
+  }
+
+  /// A type usage created by treating the current type usage as the element
+  /// type of an array.
+  /// - Returns: A type usage for the array.
+  var asArray: Self { TypeUsage(wrapped: .usage(self), usage: .array) }
+
+  /// A type usage created by treating the current type usage as the value
+  /// type of a dictionary.
+  /// - Returns: A type usage for the dictionary.
+  var asDictionaryValue: Self { TypeUsage(wrapped: .usage(self), usage: .dictionaryValue) }
+
+  /// A type usage created by wrapping the current type usage inside the
+  /// wrapper type, where the wrapper type is generic over the current type.
+  func asWrapped(in wrapper: TypeName) -> Self {
+    TypeUsage(wrapped: .usage(self), usage: .generic(wrapper: wrapper))
+  }
+}
+
+extension TypeName {
+
+  /// A type usage that wraps the current type name without changing it.
+  var asUsage: TypeUsage { TypeUsage(wrapped: .name(self), usage: .identity) }
+}
+
+extension ExistingTypeDescription {
+
+  /// Creates a new type description from the provided type usage's wrapped
+  /// value.
+  /// - Parameter wrapped: The wrapped value.
+  private init(_ wrapped: TypeUsage.Wrapped) {
+    switch wrapped {
+    case .name(let typeName): self = .init(typeName)
+    case .usage(let typeUsage): self = .init(typeUsage)
+    }
+  }
+
+  /// Creates a new type description from the provided type name.
+  /// - Parameter typeName: A type name.
+  init(_ typeName: TypeName) { self = .member(typeName.components) }
+
+  /// Creates a new type description from the provided type usage.
+  /// - Parameter typeUsage: A type usage.
+  init(_ typeUsage: TypeUsage) {
+    switch typeUsage.usage {
+    case .generic(wrapper: let wrapper):
+      self = .generic(wrapper: .init(wrapper), wrapped: .init(typeUsage.wrapped))
+    case .optional: self = .optional(.init(typeUsage.wrapped))
+    case .identity: self = .init(typeUsage.wrapped)
+    case .array: self = .array(.init(typeUsage.wrapped))
+    case .dictionaryValue: self = .dictionaryValue(.init(typeUsage.wrapped))
+    }
+  }
+}