From fdcfca806b7b227d89cf4ec3bcb53ed94be2a7c8 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Tue, 28 Nov 2023 19:52:40 +0200 Subject: [PATCH 1/4] Added TypeName and TypeUsage from OpenAPI Generator. 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. --- NOTICES.txt | 4 +- Sources/GRPCCodeGen/Internal/TypeName.swift | 127 ++++++++++ Sources/GRPCCodeGen/Internal/TypeUsage.swift | 232 +++++++++++++++++++ 3 files changed, 362 insertions(+), 1 deletion(-) create mode 100644 Sources/GRPCCodeGen/Internal/TypeName.swift create mode 100644 Sources/GRPCCodeGen/Internal/TypeUsage.swift diff --git a/NOTICES.txt b/NOTICES.txt index f089e7948..17394ee7c 100644 --- a/NOTICES.txt +++ b/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 + diff --git a/Sources/GRPCCodeGen/Internal/TypeName.swift b/Sources/GRPCCodeGen/Internal/TypeName.swift new file mode 100644 index 000000000..519cdac51 --- /dev/null +++ b/Sources/GRPCCodeGen/Internal/TypeName.swift @@ -0,0 +1,127 @@ +/* + * 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 { + + /// Describes a single component of the Swift path. + struct Component: Hashable { + /// The name of the Swift path component. + var swift: String? + } + + /// A list of components that make up the type name. + private let components: [Component] + + /// The list of Swift path components. + var swiftKeyPathComponents: [String] { components.compactMap(\.swift) } + + /// Creates a new type name with the specified list of components. + /// - Parameter components: A list of components for the type. + init(components: [Component]) { + precondition(!components.compactMap(\.swift).isEmpty, "TypeName Swift key path cannot be empty") + self.components = components + } + + /// Creates a new type name with the specified list of Swift path + /// components. + /// + /// - Parameter swiftKeyPath: A list of Swift path components for the type. + init(swiftKeyPath: [String]) { + precondition(!swiftKeyPath.isEmpty, "TypeName Swift key path cannot be empty") + self.init(components: swiftKeyPath.map { .init(swift: $0) }) + } + + /// A string representation of the fully qualified Swift type name. + /// + /// For example: `Swift.Int`. + var fullyQualifiedSwiftName: String { swiftKeyPathComponents.joined(separator: ".") } + + /// A string representation of the last path component of the Swift + /// type name. + /// + /// For example: `Int`. + var shortSwiftName: String { swiftKeyPathComponents.last! } + + /// Returns a type name by appending the specified components to the + /// current type name. + /// + /// In other words, returns a type name for a child type. + /// - Precondition: At least one of the components must be non-nil. + /// - Parameters: + /// - swiftComponent: The name of the Swift type component. + /// - Returns: A new type name. + func appending(swiftComponent: String? = nil) -> Self { + precondition(swiftComponent != nil, "The Swift name must be non-nil.") + let newComponent = Component(swift: swiftComponent) + return .init(components: components + [newComponent]) + } + + /// 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(swiftKeyPath: ["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(swiftKeyPath: ["Foundation", name]) + } +} diff --git a/Sources/GRPCCodeGen/Internal/TypeUsage.swift b/Sources/GRPCCodeGen/Internal/TypeUsage.swift new file mode 100644 index 000000000..d99113cc9 --- /dev/null +++ b/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`. + case generic(wrapper: TypeName) + } + + /// The type usage applied to the underlying type. + fileprivate var usage: Usage +} + +extension TypeUsage: CustomStringConvertible { var description: String { fullyQualifiedSwiftName } } + +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 shortSwiftName: String { + let component: String + switch wrapped { + case let .name(typeName): component = typeName.shortSwiftName + case let .usage(usage): component = usage.shortSwiftName + } + return applied(to: component) + } + + /// A string representation of the fully qualified Swift type name. + /// + /// For example: `Swift.Int`. + var fullyQualifiedSwiftName: String { + let component: String + switch wrapped { + case let .name(typeName): component = typeName.fullyQualifiedSwiftName + case let .usage(usage): component = usage.fullyQualifiedSwiftName + } + return applied(to: component) + } + + /// A string representation of the fully qualified Swift type name, with + /// any optional wrapping removed. + /// + /// For example: `Swift.Int`. + var fullyQualifiedNonOptionalSwiftName: String { withOptional(false).fullyQualifiedSwiftName } + + /// 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.fullyQualifiedSwiftName)<" + 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.swiftKeyPathComponents) } + + /// 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)) + } + } +} From 130cef2101a2ce869dfa776100b76809e1dc3b0c Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Wed, 29 Nov 2023 10:27:19 +0200 Subject: [PATCH 2/4] replaced Component type with String in TypeName --- Sources/GRPCCodeGen/Internal/TypeName.swift | 42 ++++++--------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/Sources/GRPCCodeGen/Internal/TypeName.swift b/Sources/GRPCCodeGen/Internal/TypeName.swift index 519cdac51..f2c74336b 100644 --- a/Sources/GRPCCodeGen/Internal/TypeName.swift +++ b/Sources/GRPCCodeGen/Internal/TypeName.swift @@ -34,45 +34,26 @@ import Foundation /// Use the type name to define a type, see also `TypeUsage` when referring /// to a type. struct TypeName: Hashable { - - /// Describes a single component of the Swift path. - struct Component: Hashable { - /// The name of the Swift path component. - var swift: String? - } - /// A list of components that make up the type name. - private let components: [Component] - - /// The list of Swift path components. - var swiftKeyPathComponents: [String] { components.compactMap(\.swift) } + 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: [Component]) { - precondition(!components.compactMap(\.swift).isEmpty, "TypeName Swift key path cannot be empty") + init(components: [String]) { + precondition(!components.isEmpty, "TypeName path cannot be empty") self.components = components } - /// Creates a new type name with the specified list of Swift path - /// components. - /// - /// - Parameter swiftKeyPath: A list of Swift path components for the type. - init(swiftKeyPath: [String]) { - precondition(!swiftKeyPath.isEmpty, "TypeName Swift key path cannot be empty") - self.init(components: swiftKeyPath.map { .init(swift: $0) }) - } - /// A string representation of the fully qualified Swift type name. /// /// For example: `Swift.Int`. - var fullyQualifiedSwiftName: String { swiftKeyPathComponents.joined(separator: ".") } + var fullyQualifiedName: String { components.joined(separator: ".") } /// A string representation of the last path component of the Swift /// type name. /// /// For example: `Int`. - var shortSwiftName: String { swiftKeyPathComponents.last! } + var shortName: String { components.last! } /// Returns a type name by appending the specified components to the /// current type name. @@ -80,12 +61,11 @@ struct TypeName: Hashable { /// In other words, returns a type name for a child type. /// - Precondition: At least one of the components must be non-nil. /// - Parameters: - /// - swiftComponent: The name of the Swift type component. + /// - component: The name of the Swift type component. /// - Returns: A new type name. - func appending(swiftComponent: String? = nil) -> Self { - precondition(swiftComponent != nil, "The Swift name must be non-nil.") - let newComponent = Component(swift: swiftComponent) - return .init(components: components + [newComponent]) + func appending(component: String? = nil) -> Self { + precondition(component != nil, "The type name must be non-nil.") + return .init(components: components + [component]) } /// Returns a type name by removing the last component from the current @@ -115,13 +95,13 @@ extension TypeName { /// 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(swiftKeyPath: ["Swift", name]) } + 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(swiftKeyPath: ["Foundation", name]) + TypeName(components: ["Foundation", name]) } } From 62661731339d2195249d890903c8bf1f4a020923 Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Wed, 29 Nov 2023 10:52:04 +0200 Subject: [PATCH 3/4] updated TypeUsage --- Sources/GRPCCodeGen/Internal/TypeUsage.swift | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/GRPCCodeGen/Internal/TypeUsage.swift b/Sources/GRPCCodeGen/Internal/TypeUsage.swift index d99113cc9..2fdf0a8fa 100644 --- a/Sources/GRPCCodeGen/Internal/TypeUsage.swift +++ b/Sources/GRPCCodeGen/Internal/TypeUsage.swift @@ -87,7 +87,7 @@ struct TypeUsage { fileprivate var usage: Usage } -extension TypeUsage: CustomStringConvertible { var description: String { fullyQualifiedSwiftName } } +extension TypeUsage: CustomStringConvertible { var description: String { fullyQualifiedName } } extension TypeUsage { @@ -100,23 +100,23 @@ extension TypeUsage { /// A string representation of the last component of the Swift type name. /// /// For example: `Int`. - var shortSwiftName: String { + var shortName: String { let component: String switch wrapped { - case let .name(typeName): component = typeName.shortSwiftName - case let .usage(usage): component = usage.shortSwiftName + 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 Swift type name. + /// A string representation of the fully qualified type name. /// /// For example: `Swift.Int`. - var fullyQualifiedSwiftName: String { + var fullyQualifiedName: String { let component: String switch wrapped { - case let .name(typeName): component = typeName.fullyQualifiedSwiftName - case let .usage(usage): component = usage.fullyQualifiedSwiftName + case let .name(typeName): component = typeName.fullyQualifiedName + case let .usage(usage): component = usage.fullyQualifiedName } return applied(to: component) } @@ -125,7 +125,7 @@ extension TypeUsage { /// any optional wrapping removed. /// /// For example: `Swift.Int`. - var fullyQualifiedNonOptionalSwiftName: String { withOptional(false).fullyQualifiedSwiftName } + var fullyQualifiedNonOptionalName: String { withOptional(false).fullyQualifiedName } /// Returns a string representation of the type usage applied to the /// specified Swift path component. @@ -138,7 +138,7 @@ extension TypeUsage { case .array: return "[" + component + "]" case .dictionaryValue: return "[String: " + component + "]" case .generic(wrapper: let wrapper): - return "\(wrapper.fullyQualifiedSwiftName)<" + component + ">" + return "\(wrapper.fullyQualifiedName)<" + component + ">" } } @@ -215,7 +215,7 @@ extension ExistingTypeDescription { /// Creates a new type description from the provided type name. /// - Parameter typeName: A type name. - init(_ typeName: TypeName) { self = .member(typeName.swiftKeyPathComponents) } + init(_ typeName: TypeName) { self = .member(typeName.components) } /// Creates a new type description from the provided type usage. /// - Parameter typeUsage: A type usage. From 38f81f0b5cb4bdfd234ee8873780a7ca92b9ba0a Mon Sep 17 00:00:00 2001 From: Stefana Dranca Date: Wed, 29 Nov 2023 12:31:06 +0200 Subject: [PATCH 4/4] appending argument can't be nil --- Sources/GRPCCodeGen/Internal/TypeName.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/GRPCCodeGen/Internal/TypeName.swift b/Sources/GRPCCodeGen/Internal/TypeName.swift index f2c74336b..4340baa23 100644 --- a/Sources/GRPCCodeGen/Internal/TypeName.swift +++ b/Sources/GRPCCodeGen/Internal/TypeName.swift @@ -55,16 +55,14 @@ struct TypeName: Hashable { /// For example: `Int`. var shortName: String { components.last! } - /// Returns a type name by appending the specified components to the + /// 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. - /// - Precondition: At least one of the components must be non-nil. /// - Parameters: /// - component: The name of the Swift type component. /// - Returns: A new type name. - func appending(component: String? = nil) -> Self { - precondition(component != nil, "The type name must be non-nil.") + func appending(component: String) -> Self { return .init(components: components + [component]) }