diff --git a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift index 0900169c0..e1fac9546 100644 --- a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift +++ b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift @@ -740,10 +740,31 @@ struct TextBasedRenderer: RendererProtocol { } writer.writeLine("struct \(structDesc.name)") writer.nextLineAppendsToLastLine() + let generics = structDesc.generics + if !generics.isEmpty { + writer.nextLineAppendsToLastLine() + writer.writeLine("<") + for (genericType, isLast) in generics.enumeratedWithLastMarker() { + writer.nextLineAppendsToLastLine() + renderExistingTypeDescription(genericType) + if !isLast { + writer.nextLineAppendsToLastLine() + writer.writeLine(", ") + } + } + writer.nextLineAppendsToLastLine() + writer.writeLine(">") + writer.nextLineAppendsToLastLine() + } if !structDesc.conformances.isEmpty { writer.writeLine(": \(structDesc.conformances.joined(separator: ", "))") writer.nextLineAppendsToLastLine() } + if let whereClause = structDesc.whereClause { + writer.nextLineAppendsToLastLine() + writer.writeLine(" " + renderedWhereClause(whereClause)) + writer.nextLineAppendsToLastLine() + } writer.writeLine(" {") if !structDesc.members.isEmpty { writer.withNestedLevel { diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift index ddb340c66..8db1cca87 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift +++ b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift @@ -401,11 +401,17 @@ struct StructDescription: Equatable, Codable, Sendable { /// For example, in `struct Foo {`, `name` is `Foo`. var name: String + /// The generic types of the struct. + var generics: [ExistingTypeDescription] = [] + /// The type names that the struct conforms to. /// /// For example: `["Sendable", "Codable"]`. var conformances: [String] = [] + /// A where clause constraining the struct declaration. + var whereClause: WhereClause? = nil + /// The declarations that make up the main struct body. var members: [Declaration] = [] } diff --git a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift b/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift index 65b5eb79b..036eb41e9 100644 --- a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift @@ -820,12 +820,62 @@ final class Test_TextBasedRenderer: XCTestCase { func testStruct() throws { try _test( - .init(name: "Structy"), + StructDescription(name: "Structy"), renderedBy: { $0.renderStruct(_:) }, rendersAs: #""" struct Structy {} """# ) + try _test( + StructDescription( + name: "Structy", + conformances: ["Foo"] + ), + renderedBy: { $0.renderStruct(_:) }, + rendersAs: #""" + struct Structy: Foo {} + """# + ) + try _test( + StructDescription( + name: "Structy", + generics: [.member("T")] + ), + renderedBy: { $0.renderStruct(_:) }, + rendersAs: #""" + struct Structy {} + """# + ) + try _test( + StructDescription( + name: "Structy", + generics: [.member("T")], + whereClause: WhereClause( + requirements: [ + .conformance("T", "Foo"), + .conformance("T", "Sendable"), + ] + ) + ), + renderedBy: { + $0.renderStruct(_:) + }, + rendersAs: #""" + struct Structy where T: Foo, T: Sendable {} + """# + ) + try _test( + StructDescription( + name: "Structy", + generics: [.member("T")], + conformances: ["Hashable"], + whereClause: WhereClause(requirements: [.conformance("T", "Foo")]) + ), + renderedBy: { $0.renderStruct(_:) }, + rendersAs: #""" + struct Structy: Hashable where T: Foo {} + """# + ) } func testProtocol() throws {