Skip to content

Commit f7641a0

Browse files
[CodeGenLib] Translating dependencies into StructuredSwiftRepresentation (#1752)
Translating dependencies into StructuredSwiftRepresentation Motivation: Dependency representations within CodeGeneratorRequest and StructuredSwiftRepresentation don't match (each contains fields that the other one doesn't have). The CodeGenerationRequest representation of a dependency doesn't contain preconcurrency requirements and spi, while the StructuredSwiftRepresentation doesn't allow items imports (func, class etc.). We need to add the missing fields to both representation in order to translate the imports, without losing information. Modifications: - Added the preconcurrency ans spi parameters to the CodeGenerationRequest Dependency struct (and the PreconcurrencyRequirement struct to represent that encapsulates the possible requirement types) - Added the item property to the StructuredSwiftRepresentation ImportDescription struct (and the Item struct backed up by an item representing the possible cases). - Created the function that translates a CodeGenerationRequest.Dependency into a StructuredSwiftRepresentation.ImportDescription. - Added the item rendering in the TextBasedRenderer. - Added tests for the TextRenderer and the IDLToStructuredSwiftRepresentationTranslator that check the imports. Result: Imports can now be translated and added in the generated code.
1 parent 53855b1 commit f7641a0

File tree

8 files changed

+316
-7
lines changed

8 files changed

+316
-7
lines changed

Sources/GRPCCodeGen/CodeGenError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ extension CodeGenError {
3838
private enum Value {
3939
case nonUniqueServiceName
4040
case nonUniqueMethodName
41+
case invalidKind
4142
}
4243

4344
private var value: Value
@@ -54,6 +55,11 @@ extension CodeGenError {
5455
public static var nonUniqueMethodName: Self {
5556
Self(.nonUniqueMethodName)
5657
}
58+
59+
/// An invalid kind name is used for an import.
60+
public static var invalidKind: Self {
61+
Self(.invalidKind)
62+
}
5763
}
5864
}
5965

Sources/GRPCCodeGen/CodeGenerationRequest.swift

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,25 @@ public struct CodeGenerationRequest {
8888
/// The name of the imported module or of the module an item is imported from.
8989
public var module: String
9090

91-
public init(item: Item? = nil, module: String) {
91+
/// The name of the private interface for an `@_spi` import.
92+
///
93+
/// For example, if `spi` was "Secret" and the module name was "Foo" then the import
94+
/// would be `@_spi(Secret) import Foo`.
95+
public var spi: String?
96+
97+
/// Requirements for the `@preconcurrency` attribute.
98+
public var preconcurrency: PreconcurrencyRequirement
99+
100+
public init(
101+
item: Item? = nil,
102+
module: String,
103+
spi: String? = nil,
104+
preconcurrency: PreconcurrencyRequirement = .notRequired
105+
) {
92106
self.item = item
93107
self.module = module
108+
self.spi = spi
109+
self.preconcurrency = preconcurrency
94110
}
95111

96112
/// Represents an item imported from a module.
@@ -109,7 +125,7 @@ public struct CodeGenerationRequest {
109125
/// Represents the imported item's kind.
110126
public struct Kind {
111127
/// Describes the keyword associated with the imported item.
112-
internal enum Value {
128+
internal enum Value: String {
113129
case `typealias`
114130
case `struct`
115131
case `class`
@@ -167,6 +183,36 @@ public struct CodeGenerationRequest {
167183
}
168184
}
169185
}
186+
187+
/// Describes any requirement for the `@preconcurrency` attribute.
188+
public struct PreconcurrencyRequirement {
189+
internal enum Value {
190+
case required
191+
case notRequired
192+
case requiredOnOS([String])
193+
}
194+
195+
internal var value: Value
196+
197+
internal init(_ value: Value) {
198+
self.value = value
199+
}
200+
201+
/// The attribute is always required.
202+
public static var required: Self {
203+
Self(.required)
204+
}
205+
206+
/// The attribute is not required.
207+
public static var notRequired: Self {
208+
Self(.notRequired)
209+
}
210+
211+
/// The attribute is required only on the named operating systems.
212+
public static func requiredOnOS(_ OSs: [String]) -> PreconcurrencyRequirement {
213+
return Self(.requiredOnOS(OSs))
214+
}
215+
}
170216
}
171217

172218
/// Represents a service described in an IDL file.

Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ struct TextBasedRenderer: RendererProtocol {
169169
func render(preconcurrency: Bool) {
170170
let spiPrefix = description.spi.map { "@_spi(\($0)) " } ?? ""
171171
let preconcurrencyPrefix = preconcurrency ? "@preconcurrency " : ""
172-
if let moduleTypes = description.moduleTypes {
172+
173+
if let item = description.item {
174+
writer.writeLine(
175+
"\(preconcurrencyPrefix)\(spiPrefix)import \(item.kind) \(description.moduleName).\(item.name)"
176+
)
177+
} else if let moduleTypes = description.moduleTypes {
173178
for type in moduleTypes {
174179
writer.writeLine("\(preconcurrencyPrefix)\(spiPrefix)import \(type)")
175180
}

Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
///
3232
/// For example: `import Foo`.
3333
struct ImportDescription: Equatable, Codable {
34-
3534
/// The name of the imported module.
3635
///
3736
/// For example, the `Foo` in `import Foo`.
@@ -51,6 +50,10 @@ struct ImportDescription: Equatable, Codable {
5150
/// Requirements for the `@preconcurrency` attribute.
5251
var preconcurrency: PreconcurrencyRequirement = .never
5352

53+
/// If the dependency is an item, the property's value is the item representation.
54+
/// If the dependency is a module, this property is nil.
55+
var item: Item? = nil
56+
5457
/// Describes any requirement for the `@preconcurrency` attribute.
5558
enum PreconcurrencyRequirement: Equatable, Codable {
5659
/// The attribute is always required.
@@ -60,6 +63,31 @@ struct ImportDescription: Equatable, Codable {
6063
/// The attribute is required only on the named operating systems.
6164
case onOS([String])
6265
}
66+
67+
/// Represents an item imported from a module.
68+
struct Item: Equatable, Codable {
69+
/// The keyword that specifies the item's kind (e.g. `func`, `struct`).
70+
var kind: Kind
71+
72+
/// The name of the imported item.
73+
var name: String
74+
75+
init(kind: Kind, name: String) {
76+
self.kind = kind
77+
self.name = name
78+
}
79+
}
80+
81+
enum Kind: String, Equatable, Codable {
82+
case `typealias`
83+
case `struct`
84+
case `class`
85+
case `enum`
86+
case `protocol`
87+
case `let`
88+
case `var`
89+
case `func`
90+
}
6391
}
6492

6593
/// A description of an access modifier.

Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ struct IDLToStructuredSwiftTranslator: Translator {
2323
try self.validateInput(codeGenerationRequest)
2424
let typealiasTranslator = TypealiasTranslator(client: client, server: server)
2525
let topComment = Comment.doc(codeGenerationRequest.leadingTrivia)
26-
let imports: [ImportDescription] = [
27-
ImportDescription(moduleName: "GRPCCore")
28-
]
26+
let imports = try codeGenerationRequest.dependencies.reduce(
27+
into: [ImportDescription(moduleName: "GRPCCore")]
28+
) { partialResult, newDependency in
29+
try partialResult.append(translateImport(dependency: newDependency))
30+
}
2931

3032
var codeBlocks: [CodeBlock] = []
3133
codeBlocks.append(
@@ -58,6 +60,35 @@ struct IDLToStructuredSwiftTranslator: Translator {
5860
}
5961

6062
extension IDLToStructuredSwiftTranslator {
63+
private func translateImport(
64+
dependency: CodeGenerationRequest.Dependency
65+
) throws -> ImportDescription {
66+
var importDescription = ImportDescription(moduleName: dependency.module)
67+
if let item = dependency.item {
68+
if let matchedKind = ImportDescription.Kind(rawValue: item.kind.value.rawValue) {
69+
importDescription.item = ImportDescription.Item(kind: matchedKind, name: item.name)
70+
} else {
71+
throw CodeGenError(
72+
code: .invalidKind,
73+
message: "Invalid kind name for import: \(item.kind.value.rawValue)"
74+
)
75+
}
76+
}
77+
if let spi = dependency.spi {
78+
importDescription.spi = spi
79+
}
80+
81+
switch dependency.preconcurrency.value {
82+
case .required:
83+
importDescription.preconcurrency = .always
84+
case .notRequired:
85+
importDescription.preconcurrency = .never
86+
case .requiredOnOS(let OSs):
87+
importDescription.preconcurrency = .onOS(OSs)
88+
}
89+
return importDescription
90+
}
91+
6192
private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws {
6293
let servicesByNamespace = Dictionary(
6394
grouping: codeGenerationRequest.services,

Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,60 @@ final class Test_TextBasedRenderer: XCTestCase {
130130
@preconcurrency @_spi(Secret) import Bar
131131
"""#
132132
)
133+
134+
try _test(
135+
[
136+
ImportDescription(
137+
moduleName: "Foo",
138+
item: ImportDescription.Item(kind: .typealias, name: "Bar")
139+
),
140+
ImportDescription(
141+
moduleName: "Foo",
142+
item: ImportDescription.Item(kind: .struct, name: "Baz")
143+
),
144+
ImportDescription(
145+
moduleName: "Foo",
146+
item: ImportDescription.Item(kind: .class, name: "Bac")
147+
),
148+
ImportDescription(
149+
moduleName: "Foo",
150+
item: ImportDescription.Item(kind: .enum, name: "Bap")
151+
),
152+
ImportDescription(
153+
moduleName: "Foo",
154+
item: ImportDescription.Item(kind: .protocol, name: "Bat")
155+
),
156+
ImportDescription(moduleName: "Foo", item: ImportDescription.Item(kind: .let, name: "Bam")),
157+
ImportDescription(moduleName: "Foo", item: ImportDescription.Item(kind: .var, name: "Bag")),
158+
ImportDescription(
159+
moduleName: "Foo",
160+
item: ImportDescription.Item(kind: .func, name: "Bak")
161+
),
162+
ImportDescription(
163+
moduleName: "Foo",
164+
spi: "Secret",
165+
item: ImportDescription.Item(kind: .func, name: "SecretBar")
166+
),
167+
ImportDescription(
168+
moduleName: "Foo",
169+
preconcurrency: .always,
170+
item: ImportDescription.Item(kind: .func, name: "PreconcurrencyBar")
171+
),
172+
],
173+
renderedBy: TextBasedRenderer.renderImports,
174+
rendersAs: #"""
175+
import typealias Foo.Bar
176+
import struct Foo.Baz
177+
import class Foo.Bac
178+
import enum Foo.Bap
179+
import protocol Foo.Bat
180+
import let Foo.Bam
181+
import var Foo.Bag
182+
import func Foo.Bak
183+
@_spi(Secret) import func Foo.SecretBar
184+
@preconcurrency import func Foo.PreconcurrencyBar
185+
"""#
186+
)
133187
}
134188

135189
func testAccessModifiers() throws {

Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,128 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase {
2424
typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor
2525
typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor
2626

27+
func testImports() throws {
28+
var dependencies = [CodeGenerationRequest.Dependency]()
29+
dependencies.append(CodeGenerationRequest.Dependency(module: "Foo"))
30+
dependencies.append(
31+
CodeGenerationRequest.Dependency(item: .init(kind: .typealias, name: "Bar"), module: "Foo")
32+
)
33+
dependencies.append(
34+
CodeGenerationRequest.Dependency(item: .init(kind: .struct, name: "Baz"), module: "Foo")
35+
)
36+
dependencies.append(
37+
CodeGenerationRequest.Dependency(item: .init(kind: .class, name: "Bac"), module: "Foo")
38+
)
39+
dependencies.append(
40+
CodeGenerationRequest.Dependency(item: .init(kind: .enum, name: "Bap"), module: "Foo")
41+
)
42+
dependencies.append(
43+
CodeGenerationRequest.Dependency(item: .init(kind: .protocol, name: "Bat"), module: "Foo")
44+
)
45+
dependencies.append(
46+
CodeGenerationRequest.Dependency(item: .init(kind: .let, name: "Baq"), module: "Foo")
47+
)
48+
dependencies.append(
49+
CodeGenerationRequest.Dependency(item: .init(kind: .var, name: "Bag"), module: "Foo")
50+
)
51+
dependencies.append(
52+
CodeGenerationRequest.Dependency(item: .init(kind: .func, name: "Bak"), module: "Foo")
53+
)
54+
55+
let expectedSwift =
56+
"""
57+
/// Some really exciting license header 2023.
58+
import GRPCCore
59+
import Foo
60+
import typealias Foo.Bar
61+
import struct Foo.Baz
62+
import class Foo.Bac
63+
import enum Foo.Bap
64+
import protocol Foo.Bat
65+
import let Foo.Baq
66+
import var Foo.Bag
67+
import func Foo.Bak
68+
"""
69+
try self.assertIDLToStructuredSwiftTranslation(
70+
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
71+
expectedSwift: expectedSwift
72+
)
73+
}
74+
75+
func testPreconcurrencyImports() throws {
76+
var dependencies = [CodeGenerationRequest.Dependency]()
77+
dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", preconcurrency: .required))
78+
dependencies.append(
79+
CodeGenerationRequest.Dependency(
80+
item: .init(kind: .enum, name: "Bar"),
81+
module: "Foo",
82+
preconcurrency: .required
83+
)
84+
)
85+
dependencies.append(
86+
CodeGenerationRequest.Dependency(
87+
module: "Baz",
88+
preconcurrency: .requiredOnOS(["Deq", "Der"])
89+
)
90+
)
91+
let expectedSwift =
92+
"""
93+
/// Some really exciting license header 2023.
94+
import GRPCCore
95+
@preconcurrency import Foo
96+
@preconcurrency import enum Foo.Bar
97+
#if os(Deq) || os(Der)
98+
@preconcurrency import Baz
99+
#else
100+
import Baz
101+
#endif
102+
"""
103+
try self.assertIDLToStructuredSwiftTranslation(
104+
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
105+
expectedSwift: expectedSwift
106+
)
107+
}
108+
109+
func testSPIImports() throws {
110+
var dependencies = [CodeGenerationRequest.Dependency]()
111+
dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret"))
112+
dependencies.append(
113+
CodeGenerationRequest.Dependency(
114+
item: .init(kind: .enum, name: "Bar"),
115+
module: "Foo",
116+
spi: "Secret"
117+
)
118+
)
119+
120+
let expectedSwift =
121+
"""
122+
/// Some really exciting license header 2023.
123+
import GRPCCore
124+
@_spi(Secret) import Foo
125+
@_spi(Secret) import enum Foo.Bar
126+
"""
127+
try self.assertIDLToStructuredSwiftTranslation(
128+
codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies),
129+
expectedSwift: expectedSwift
130+
)
131+
}
132+
133+
private func assertIDLToStructuredSwiftTranslation(
134+
codeGenerationRequest: CodeGenerationRequest,
135+
expectedSwift: String
136+
) throws {
137+
let translator = IDLToStructuredSwiftTranslator()
138+
let structuredSwift = try translator.translate(
139+
codeGenerationRequest: codeGenerationRequest,
140+
client: false,
141+
server: false
142+
)
143+
let renderer = TextBasedRenderer.default
144+
let sourceFile = try renderer.render(structured: structuredSwift)
145+
let contents = sourceFile.contents
146+
try XCTAssertEqualWithDiff(contents, expectedSwift)
147+
}
148+
27149
func testSameNameServicesNoNamespaceError() throws {
28150
let serviceA = ServiceDescriptor(
29151
documentation: "Documentation for AService",

0 commit comments

Comments
 (0)