Skip to content
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

Merged
merged 5 commits into from
Nov 24, 2023
Merged

Conversation

stefanadranca
Copy link
Collaborator

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.

…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
Comment on lines 516 to 519
static let grpcCodeGenLib: Product = .library(
name: "GRPCCodeGen",
targets: ["GRPCCodeGen"]
)
Copy link
Collaborator

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(
Copy link
Collaborator

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
Copy link
Collaborator

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 @@
/*
Copy link
Collaborator

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?

Copy link
Collaborator Author

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?

Copy link
Collaborator

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 {
Copy link
Collaborator

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The swift imports that this file depends on.
/// The Swift imports that the generated file should depends on.

Copy link
Collaborator

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?

Comment on lines 35 to 39
/// String representation of the serializer call.
var lookupSerializer: (String) -> String

/// String representation of the deserializer call.
var lookupDeserializer: (String) -> String
Copy link
Collaborator

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.

Comment on lines 45 to 54
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"
}
Copy link
Collaborator

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.

Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: documentation

Comment on lines 75 to 82
struct MethodDescriptor {
var name: String
var isInputStreaming: Bool
var isOutputStreaming: Bool
var inputType: String
var ouputType: String
var docs: String
}
Copy link
Collaborator

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]?
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.
Copy link
Collaborator

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider linking to ServiceDescriptor

Comment on lines 45 to 54
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"
}
Copy link
Collaborator

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).

Comment on lines 56 to 60
var kind: Kind
var name: String
}
var symbol: Symbol?
var module: String
Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, docs are missing

@glbrntt glbrntt added the v2 A change for v2 label Nov 22, 2023
/// 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.
Copy link
Collaborator

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:

Suggested change
/// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The Swift imports that the generated file should depend on.
/// The Swift imports that the generated file depends on.

Copy link
Collaborator

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.
Copy link
Collaborator

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.

Suggested change
/// 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.

Copy link
Collaborator

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
Copy link
Collaborator

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:

Suggested change
var lookupSerializer: (String) -> String
var lookupSerializer: (_ messageType: String) -> String

Comment on lines 68 to 86
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`
}
}
Copy link
Collaborator

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.
Copy link
Collaborator

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.

Comment on lines 38 to 42
/// 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)>()"}`.
Copy link
Collaborator

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.

Suggested change
/// 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)>()"
/// }
/// ```

Comment on lines 45 to 49
/// 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)>()"}`.
Copy link
Collaborator

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.

Comment on lines 54 to 55
dependencies: [Dependency] = [],
leadingTrivia: String,
Copy link
Collaborator

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:

  1. required
  2. optional
  3. 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.

Comment on lines 93 to 94
}
/// Represents the imported item's kind.
Copy link
Collaborator

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`).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The imported item's symbol / name.
/// The name of the imported item.

Comment on lines 170 to 172
/// Array of descriptors for the methods of a service.
///
/// - SeeAlso: ``MethodDescriptor``.
Copy link
Collaborator

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] = []
Copy link
Collaborator

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!

@glbrntt glbrntt enabled auto-merge (squash) November 24, 2023 13:59
@glbrntt glbrntt merged commit 9bf9d28 into grpc:main Nov 24, 2023
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v2 A change for v2
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants