Skip to content

Add parameter to control extension output #1795

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions Sources/SwiftProtobuf/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,22 +170,23 @@ extension Message {
}
}

/// Implementation base for all messages; not intended for client use.
// Legacy protocol; no longer used, but left to maintain source compatibility.
@preconcurrency
public protocol _MessageImplementationBase: _MessageEquatable, _MessageHashable {}

/// Not intended for client use.
///
/// In general, use `SwiftProtobuf.Message` instead when you need a variable or
/// argument that can hold any type of message. Occasionally, you can use
/// `SwiftProtobuf.Message & Equatable` or `SwiftProtobuf.Message & Hashable` as
/// generic constraints if you need to write generic code that can be applied to
/// multiple message types that uses equality tests, puts messages in a `Set`,
/// or uses them as `Dictionary` keys.
/// `SwiftProtobuf.Message & Equatable` as generic constraint.
@preconcurrency
public protocol _MessageImplementationBase: Message, Hashable {
public protocol _MessageEquatable: Equatable {

// Legacy function; no longer used, but left to maintain source compatibility.
func _protobuf_generated_isEqualTo(other: Self) -> Bool
}

extension _MessageImplementationBase {
extension _MessageEquatable {
public func isEqualTo(message: any Message) -> Bool {
guard let other = message as? Self else {
return false
Expand All @@ -206,3 +207,11 @@ extension _MessageImplementationBase {
self == other
}
}

/// Not intended for client use.
///
/// In general, use `SwiftProtobuf.Message` instead when you need a variable or
/// argument that can hold any type of message. Occasionally, you can use
/// `SwiftProtobuf.Message & Hashable` as generic constraint.
@preconcurrency
public protocol _MessageHashable: Hashable {}
14 changes: 7 additions & 7 deletions Sources/protoc-gen-swift/EnumGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,14 @@ class EnumGenerator {
}

func generateRuntimeSupport(printer p: inout CodePrinter) {
p.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)_ProtoNameProviding {"
)
p.withIndentation { p in
generateProtoNameProviding(printer: &p)
let nameProvidingConfiguration = generatorOptions.nameProvidingOutputConfiguration.configuration(for: swiftFullName)
nameProvidingConfiguration.generateExtension(
printer: &p,
typeFullName: swiftFullName,
extensionFullName: "\(namer.swiftProtobufModulePrefix)_ProtoNameProviding"
) { p in
self.generateProtoNameProviding(printer: &p)
}
p.print("}")
}

/// Generates the cases or statics (for alias) for the values.
Expand Down
152 changes: 152 additions & 0 deletions Sources/protoc-gen-swift/GeneratorOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,58 @@ package class GeneratorOptions {
}
}
}

package enum ExtensionType {
case equatable
case hashable
case nameProviding

init?(flag: String) {
switch flag.lowercased() {
case "equatable":
self = .equatable
case "hashable":
self = .hashable
case "nameproviding", "name_providing":
self = .nameProviding
default:
return nil
}
}
}

package enum ExtensionOutputConfiguration {
case normal
case debugOnly
case skip

init?(flag: String) {
switch flag.lowercased() {
case "normal":
self = .normal
case "debugonly", "debug_only":
self = .debugOnly
case "skip":
self = .skip
default:
return nil
}
}
}

package struct ExtensionOutputConfigurationStorage {
var defaultValue: ExtensionOutputConfiguration
var typeSpecificValue: [String: ExtensionOutputConfiguration]
}

let outputNaming: OutputNaming
let protoToModuleMappings: ProtoFileToModuleMappings
let visibility: Visibility
let importDirective: ImportDirective
let experimentalStripNonfunctionalCodegen: Bool
let equatableOutputConfiguration: ExtensionOutputConfigurationStorage
let hashableOutputConfiguration: ExtensionOutputConfigurationStorage
let nameProvidingOutputConfiguration: ExtensionOutputConfigurationStorage

/// A string snippet to insert for the visibility
let visibilitySourceSnippet: String
Expand All @@ -81,6 +127,18 @@ package class GeneratorOptions {
var implementationOnlyImports: Bool = false
var useAccessLevelOnImports = false
var experimentalStripNonfunctionalCodegen: Bool = false
var equatableOutputConfiguration = ExtensionOutputConfigurationStorage(
defaultValue: .normal,
typeSpecificValue: [:]
)
var hashableOutputConfiguration = ExtensionOutputConfigurationStorage(
defaultValue: .normal,
typeSpecificValue: [:]
)
var nameProvidingOutputConfiguration = ExtensionOutputConfigurationStorage(
defaultValue: .normal,
typeSpecificValue: [:]
)

for pair in parameter.parsedPairs {
switch pair.key {
Expand Down Expand Up @@ -146,6 +204,54 @@ package class GeneratorOptions {
value: pair.value
)
}
case "GenerateExtensions":
for config in pair.value.split(separator: ";") {
let configPair = config.split(separator: "=")
if configPair.count == 2 {
let extensionType: ExtensionType?
var typeName: String?

let configKey = configPair[0].split(separator: ":")
if configKey.count == 1 {
extensionType = ExtensionType(flag: String(configKey[0]))
} else if configKey.count == 2 {
typeName = String(configKey[0])
extensionType = ExtensionType(flag: String(configKey[1]))
} else {
continue
}

let outputValue = ExtensionOutputConfiguration(flag: String(configPair[1]))

guard
let extensionType = extensionType,
let outputValue = outputValue
else {
continue
}

if let typeName = typeName {
switch extensionType {
case .equatable:
equatableOutputConfiguration.typeSpecificValue[typeName] = outputValue
case .hashable:
hashableOutputConfiguration.typeSpecificValue[typeName] = outputValue
case .nameProviding:
nameProvidingOutputConfiguration.typeSpecificValue[typeName] = outputValue
}
} else {
switch extensionType {
case .equatable:
equatableOutputConfiguration.defaultValue = outputValue
case .hashable:
hashableOutputConfiguration.defaultValue = outputValue
case .nameProviding:
nameProvidingOutputConfiguration.defaultValue = outputValue
}
}
}
}
break
default:
throw GenerationError.unknownParameter(name: pair.key)
}
Expand Down Expand Up @@ -224,5 +330,51 @@ package class GeneratorOptions {
// this out via the compiler errors around missing type (when bar.pb.swift
// gets unknown reference for thing that should be in module One via
// foo.pb.swift).

self.equatableOutputConfiguration = equatableOutputConfiguration
self.hashableOutputConfiguration = hashableOutputConfiguration
self.nameProvidingOutputConfiguration = nameProvidingOutputConfiguration
}
}

extension GeneratorOptions.ExtensionOutputConfigurationStorage {
func configuration(for typeName: String) -> GeneratorOptions.ExtensionOutputConfiguration {
return typeSpecificValue[typeName] ?? defaultValue
}
}

extension GeneratorOptions.ExtensionOutputConfiguration {
func generateExtension(
printer p: inout CodePrinter,
typeFullName: String,
extensionFullName: String,
extensionBody: ((_ p: inout CodePrinter) -> Void)?
) {
switch self {
case .normal:
p.print(
"",
"extension \(typeFullName): \(extensionFullName) {"
)
p.withIndentation { p in
extensionBody?(&p)
}
p.print("}")
case .debugOnly:
p.print(
"",
"#if DEBUG",
"extension \(typeFullName): \(extensionFullName) {"
)
p.withIndentation { p in
extensionBody?(&p)
}
p.print(
"}",
"#endif"
)
case .skip:
return
}
}
}
51 changes: 39 additions & 12 deletions Sources/protoc-gen-swift/MessageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,47 @@ class MessageGenerator {
}

func generateRuntimeSupport(printer p: inout CodePrinter, file: FileGenerator, parent: MessageGenerator?) {
generateMessageExtension(printer: &p, file: file, parent: parent)

let equatableConfiguration = generatorOptions.equatableOutputConfiguration.configuration(for: swiftFullName)
equatableConfiguration.generateExtension(
printer: &p,
typeFullName: swiftFullName,
extensionFullName: "\(namer.swiftProtobufModulePrefix)_MessageEquatable"
) { p in
self.generateMessageEquality(printer: &p)
}

let hashableConfiguration = generatorOptions.hashableOutputConfiguration.configuration(for: swiftFullName)
hashableConfiguration.generateExtension(
printer: &p,
typeFullName: swiftFullName,
extensionFullName: "\(namer.swiftProtobufModulePrefix)_MessageHashable",
extensionBody: nil
)

let nameProvidingConfiguration = generatorOptions.nameProvidingOutputConfiguration.configuration(for: swiftFullName)
nameProvidingConfiguration.generateExtension(
printer: &p,
typeFullName: swiftFullName,
extensionFullName: "\(namer.swiftProtobufModulePrefix)_ProtoNameProviding",
) { p in
self.generateProtoNameProviding(printer: &p)
}

// Nested enums and messages
for e in enums {
e.generateRuntimeSupport(printer: &p)
}
for m in messages {
m.generateRuntimeSupport(printer: &p, file: file, parent: self)
}
}

private func generateMessageExtension(printer p: inout CodePrinter, file: FileGenerator, parent: MessageGenerator?) {
p.print(
"",
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)Message, \(namer.swiftProtobufModulePrefix)_MessageImplementationBase, \(namer.swiftProtobufModulePrefix)_ProtoNameProviding {"
"extension \(swiftFullName): \(namer.swiftProtobufModulePrefix)Message {"
)
p.withIndentation { p in
if let parent = parent {
Expand All @@ -222,7 +260,6 @@ class MessageGenerator {
} else {
p.print("\(visibility)static let protoMessageName: String = \"\(descriptor.name)\"")
}
generateProtoNameProviding(printer: &p)
if let storage = storage {
p.print()
storage.generateTypeDeclaration(printer: &p)
Expand All @@ -235,18 +272,8 @@ class MessageGenerator {
generateDecodeMessage(printer: &p)
p.print()
generateTraverse(printer: &p)
p.print()
generateMessageEquality(printer: &p)
}
p.print("}")

// Nested enums and messages
for e in enums {
e.generateRuntimeSupport(printer: &p)
}
for m in messages {
m.generateRuntimeSupport(printer: &p, file: file, parent: self)
}
}

private func generateProtoNameProviding(printer p: inout CodePrinter) {
Expand Down