-
Notifications
You must be signed in to change notification settings - Fork 420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Created the CodeGenerationRequest #1716
Conversation
…ndencies and trivia from an IDL file, and the IDL itself through its specific serializer and deserializer. Motivation: The CodeGenerationRequest is part of the new code generator library for grpc-swift. It represents services described by IDL and is the first stage in the process of code generation. Modifications: Implemented the CodeGenerationRequest struct and added the new target and library in manifest. Result: The code generators will be able to use a general representation for the services described in different IDLs.
Package.swift
Outdated
static let grpcCodeGenLib: Product = .library( | ||
name: "GRPCCodeGen", | ||
targets: ["GRPCCodeGen"] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't add a product because we don't want to make it API yet.
Package.swift
Outdated
@@ -477,6 +478,11 @@ extension Target { | |||
.copy("Generated") | |||
] | |||
) | |||
|
|||
static let grpcCodeGenLib: Target = .target( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name should match the module name: grpcCodeGen
* limitations under the License. | ||
*/ | ||
|
||
import Foundation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use Foundation
here
@@ -0,0 +1,84 @@ | |||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this file in a layers
directory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking we can have all the representations in a directory (CodeGenerationRequest, StructuredSwiftSyntax and SourceFile). Would "Representations" be a better name, or should I not create the directory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to split them into directories based on the "area" when there are (or will be) many files. In this case I'm not sure there will be enough files so I probably wouldn't bother. One thing that can be useful though is to but internal
things in an Internal
directory.
|
||
/// Describes the services, dependencies and trivia from an IDL file, | ||
/// and the IDL itself through its specific serializer and deserializer. | ||
struct CodeGenerationRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be public
.
/// The name of the source file containing the IDL. | ||
var fileName: String | ||
|
||
/// The swift imports that this file depends on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The swift imports that this file depends on. | |
/// The Swift imports that the generated file should depends on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could maybe also use - SeeAlso:
to link to Dependency
?
/// String representation of the serializer call. | ||
var lookupSerializer: (String) -> String | ||
|
||
/// String representation of the deserializer call. | ||
var lookupDeserializer: (String) -> String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the documentation for these really explains what they are for.
enum Kind: String { | ||
case `typealias` = "typealias" | ||
case `struct` = "struct" | ||
case `class` = "class" | ||
case `enum` = "enum" | ||
case `protocol` = "protocol" | ||
case `let` = "let" | ||
case `var` = "var" | ||
case `func` = "func" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be a struct backed by an enum so we can evolve it. We also don't need it to be raw representable as a String
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, you also don't need to explicitly define the String
the case
maps to if you want it to be the same as the case name (i.e. having case typelias
without = "typealias"
works).
struct ServiceDescriptor { | ||
/// Description of the service from comments | ||
/// above the description from the IDL file. | ||
var docs: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: documentation
struct MethodDescriptor { | ||
var name: String | ||
var isInputStreaming: Bool | ||
var isOutputStreaming: Bool | ||
var inputType: String | ||
var ouputType: String | ||
var docs: String | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs documentation
var fileName: String | ||
|
||
/// The swift imports that this file depends on. | ||
var dependencies: [Dependency]? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this to be optional? An empty array can still represent no dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, it doesn't need to be optional
/// The name of the source file containing the IDL. | ||
var fileName: String | ||
|
||
/// The swift imports that this file depends on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could maybe also use - SeeAlso:
to link to Dependency
?
/// documentation and copyright headers. | ||
var leadingTrivia: String | ||
|
||
/// An array of service descriptors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider linking to ServiceDescriptor
enum Kind: String { | ||
case `typealias` = "typealias" | ||
case `struct` = "struct" | ||
case `class` = "class" | ||
case `enum` = "enum" | ||
case `protocol` = "protocol" | ||
case `let` = "let" | ||
case `var` = "var" | ||
case `func` = "func" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, you also don't need to explicitly define the String
the case
maps to if you want it to be the same as the case name (i.e. having case typelias
without = "typealias"
works).
var kind: Kind | ||
var name: String | ||
} | ||
var symbol: Symbol? | ||
var module: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some docs are missing for these props
/// above the description from the IDL file. | ||
var docs: String | ||
|
||
var name: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, docs are missing
/// Describes the services, dependencies and trivia from an IDL file, | ||
/// and the IDL itself through its specific serializer and deserializer. | ||
public struct CodeGenerationRequest { | ||
/// The name of the source file containing the IDL. It contains the file's extension. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not all files have extensions:
/// The name of the source file containing the IDL. It contains the file's extension. | |
/// The name of the source file containing the IDL, including the extension if applicable. |
/// The name of the source file containing the IDL. It contains the file's extension. | ||
var fileName: String | ||
|
||
/// The Swift imports that the generated file should depend on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The Swift imports that the generated file should depend on. | |
/// The Swift imports that the generated file depends on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you clarify in the docs that gRPC imports aren't required because we'll add those?
/// - SeeAlso: ``Dependency``. | ||
var dependencies: [Dependency] = [] | ||
|
||
/// Any comments at the top of the file including documentation and copyright headers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They don't have to include these things, we're just stating examples of what they'll be.
/// Any comments at the top of the file including documentation and copyright headers. | |
/// Any comments at the top of the file such as documentation and copyright headers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add a note here saying that we'll place these verbatim at the top of the generated file?
/// the generated code, where clients or servers serialize their output. | ||
/// | ||
/// For example: `lookupSerializer: {"ProtobufSerializer<\($0)>()"}`. | ||
var lookupSerializer: (String) -> String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See "Label tuple members and name closure parameters" https://www.swift.org/documentation/api-design-guidelines/#special-instructions:
var lookupSerializer: (String) -> String | |
var lookupSerializer: (_ messageType: String) -> String |
public struct Kind { | ||
/// One of the possible keywords associated to the imported item's kind. | ||
var keyword: Keyword | ||
|
||
public init(_ keyword: Keyword) { | ||
self.keyword = keyword | ||
} | ||
|
||
internal enum Keyword { | ||
case `typealias` | ||
case `struct` | ||
case `class` | ||
case `enum` | ||
case `protocol` | ||
case `let` | ||
case `var` | ||
case `func` | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What want Kind
to be is an enum
, but we can't do that because adding a new case
to an enum
is an API breaking change. Instead we can have a struct
backed by an enum
; internally we can have access to the enum
so we can switch over its values. However, publicly there needs to be API to create each enum case. The usual pattern is like this:
public struct Kind {
internal enum Value {
case foo
case bar
}
internal var value: Value
internal init(_ value: Value) {
self.value = value
}
public static var foo: Self {
Self(.foo)
}
public static var bar: Self {
Self(.bar)
}
}
/// They will be placed at the top of the generated file. | ||
public var leadingTrivia: String | ||
|
||
/// An array of service descriptors. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't tell me anything more than [ServiceDescriptor]
tells me (i.e. what the thing is). If you say "A description of each service to generate." then it tells the user what it's used for.
/// Closure that receives the message type and returns a string representation of the | ||
/// serializer call for that message type. The result is inserted in the string representing | ||
/// the generated code, where clients or servers serialize their output. | ||
/// | ||
/// For example: `lookupSerializer: {"ProtobufSerializer<\($0)>()"}`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs should start with a single sentence summary and add more detail in other paragraphs. Tools like Xcode and DocC show the first paragraph more prominently so it's quite important for it to be concise.
We can also be more specific about the requirements here: the serializer type must conform to MessageSerializer
.
/// Closure that receives the message type and returns a string representation of the | |
/// serializer call for that message type. The result is inserted in the string representing | |
/// the generated code, where clients or servers serialize their output. | |
/// | |
/// For example: `lookupSerializer: {"ProtobufSerializer<\($0)>()"}`. | |
/// Closure that receives a message type as a `String` and returns a code snippet to | |
/// initialize a `MessageSerializer` for that type as a `String`. | |
/// | |
/// The result is inserted in the generated code, where clients serialize RPC inputs and | |
/// servers serialize RPC outputs. | |
/// | |
/// For example, to serialize Protobuf messages you could specify a serializer as: | |
/// ```swift | |
/// request.lookupSerializer = { messageType in | |
/// "ProtobufSerializer<\(messageType)>()" | |
/// } | |
/// ``` |
/// Closure that receives the message type and returns a string representation of the | ||
/// deserializer call for that message type. The result is inserted in the string representing | ||
/// the generated code, where clients or servers deserialize their input. | ||
/// | ||
/// For example: `lookupDeserializer: {"ProtobufDeserializer<\($0)>()"}`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a similar treatment to the one above, but the deserializer must conform to MessageDeserializer
.
dependencies: [Dependency] = [], | ||
leadingTrivia: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally parameters should be specified in this order:
- required
- optional
- closures
In this case, I think we should probably just remove the defaults for dependencies
and services
. It's highly unlikely that there will be no dependencies or services.
We should also invert the order of dependencies
and leadingTrivia
, if you're reading a file top-to-bottom the leading trivial comes at the very top followed by the dependencies.
} | ||
/// Represents the imported item's kind. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: leave a space between these
|
||
/// Represents an item imported from a module. | ||
public struct Item { | ||
/// The keyword that specifies the item's kind (e.g. `func`, `struct`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The keyword that specifies the item's kind (e.g. `func`, `struct`). | |
/// The keyword that specifies the item's kind (e.g. `func`, `struct`). |
/// The keyword that specifies the item's kind (e.g. `func`, `struct`). | ||
public var kind: Kind | ||
|
||
/// The imported item's symbol / name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// The imported item's symbol / name. | |
/// The name of the imported item. |
/// Array of descriptors for the methods of a service. | ||
/// | ||
/// - SeeAlso: ``MethodDescriptor``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as on services
, this doesn't tell me anything more than [MethodDescriptor]
.
documentation: String, | ||
name: String, | ||
namespace: String, | ||
methods: [MethodDescriptor] = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should default this, It'd be very strange to generate a service with no methods!
Created the CodeGenerationRequest which represents the services, dependencies and trivia from an IDL file, and the IDL itself through its specific serializer and deserializer.
Motivation:
The CodeGenerationRequest is part of the new code generator library for grpc-swift. It represents services described by IDL and is the first stage in the process of code generation.
Modifications:
Implemented the CodeGenerationRequest struct and added the new target and library in manifest.
Result:
The code generators will be able to use a general representation for the services described in different IDLs.