Skip to content

Commit

Permalink
Implement Field and update traverse implementation to use static _fie…
Browse files Browse the repository at this point in the history
…lds var
  • Loading branch information
mrabiciu committed Nov 22, 2023
1 parent d36b824 commit 5de597d
Show file tree
Hide file tree
Showing 6 changed files with 848 additions and 37 deletions.
735 changes: 735 additions & 0 deletions Sources/SwiftProtobuf/Field.swift

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Sources/SwiftProtobuf/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public protocol Message: _CommonMessageConformances {
/// normal `Equatable`. `Equatable` is provided with specific generated
/// types.
func isEqualTo(message: Message) -> Bool

/// Provides `Field` information for this `Message` type used to provide a default implementation of `traverse`
static var _fields: [Field<Self>] { get }
}

#if DEBUG
Expand All @@ -129,6 +132,14 @@ extension Message {
return true
}

/// Default traverse implementation
public func traverse<V: Visitor>(visitor: inout V) throws {
for field in Self._fields {
try field.traverse(message: self, visitor: &visitor)
}
try unknownFields.traverse(visitor: &visitor)
}

/// A hash based on the message's full contents.
public func hash(into hasher: inout Hasher) {
var visitor = HashVisitor(hasher)
Expand Down
3 changes: 3 additions & 0 deletions Sources/protoc-gen-swift/FieldGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ protocol FieldGenerator {

/// Generate the support for traversing this field.
func generateTraverse(printer: inout CodePrinter)

/// Generate the field node
func generateFieldNode(printer: inout CodePrinter)

/// Generate support for comparing this field's value.
/// The generated code should return false in the current scope if the field's don't match.
Expand Down
28 changes: 28 additions & 0 deletions Sources/protoc-gen-swift/MessageFieldGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
private let swiftName: String
private let underscoreSwiftName: String
private let storedProperty: String
private let storedPropertyWithoutSelf: String
private let swiftHasName: String
private let swiftClearName: String
private let swiftType: String
Expand Down Expand Up @@ -75,8 +76,10 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {

if usesHeapStorage {
storedProperty = "_storage.\(underscoreSwiftName)"
storedPropertyWithoutSelf = storedProperty
} else {
storedProperty = "self.\(hasFieldPresence ? underscoreSwiftName : swiftName)"
storedPropertyWithoutSelf = "\(hasFieldPresence ? underscoreSwiftName : swiftName)"
}

super.init(descriptor: descriptor)
Expand Down Expand Up @@ -221,4 +224,29 @@ class MessageFieldGenerator: FieldGeneratorBase, FieldGenerator {
p.printIndented("try visitor.\(visitMethod)(\(traitsArg)value: \(varName), fieldNumber: \(number))")
p.print("}\(suffix)")
}

func generateFieldNode(printer p: inout SwiftProtobufPluginLibrary.CodePrinter) {
let factoryMethod: String
let traitsArg: String
if isMap {
factoryMethod = "map"
traitsArg = "type: \(traitsType).self, "
} else {
let modifier = isPacked ? "packed" : isRepeated ? "repeated" : "singular"
factoryMethod = "\(modifier)\(fieldDescriptor.protoGenericType)"
traitsArg = ""
}

let suffix: String
if isRepeated {
suffix = ""
} else if hasFieldPresence {
suffix = ", isUnset: { $0.\(storedPropertyWithoutSelf) == nil }"
} else if swiftDefaultValue != "0" && swiftDefaultValue != "false" && swiftDefaultValue != "String()" && swiftDefaultValue != "Data()" {
suffix = ", defaultValue: \(swiftDefaultValue)"
} else {
suffix = ""
}
p.print(".\(factoryMethod)(\(traitsArg){ $0.\(swiftName) }, fieldNumber: \(number)\(suffix)),")
}
}
76 changes: 39 additions & 37 deletions Sources/protoc-gen-swift/MessageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ class MessageGenerator {
// generateIsInitialized provides a blank line after itself.
generateDecodeMessage(printer: &p)
p.print()
generateFieldNodes(printer: &p)
p.print()
generateTraverse(printer: &p)
p.print()
generateMessageEquality(printer: &p)
Expand Down Expand Up @@ -311,53 +313,53 @@ class MessageGenerator {
p.print("}")
}
}

}
}
p.print("}")
}

/// Generates the `traverse` method for the message.
///
/// - Parameter p: The code printer.
private func generateTraverse(printer p: inout CodePrinter) {
p.print("\(visibility)func traverse<V: \(namer.swiftProtobufModulePrefix)Visitor>(visitor: inout V) throws {")
p.withIndentation { p in
generateWithLifetimeExtension(printer: &p, throws: true) { p in
if let storage = storage {
storage.generatePreTraverse(printer: &p)
}
private func generateFieldNodes(printer p: inout CodePrinter) {
let visitExtensionsName = descriptor.useMessageSetWireFormat ? "extensionFieldsAsMessageSet" : "extensionFields"

let visitExtensionsName =
descriptor.useMessageSetWireFormat ? "visitExtensionFieldsAsMessageSet" : "visitExtensionFields"

let usesLocals = fields.reduce(false) { $0 || $1.generateTraverseUsesLocals }
if usesLocals {
p.print("""
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
""")
}
p.print("\(visibility)static let _fields: [Field<Self>] = [")

// Use the "ambitious" ranges because for visit because subranges with no
// intermixed fields can be merged to reduce the number of calls for
// extension visitation.
var ranges = descriptor.ambitiousExtensionRanges.makeIterator()
var nextRange = ranges.next()

// Use the "ambitious" ranges because for visit because subranges with no
// intermixed fields can be merged to reduce the number of calls for
// extension visitation.
var ranges = descriptor.ambitiousExtensionRanges.makeIterator()
var nextRange = ranges.next()
for f in fieldsSortedByNumber {
while nextRange != nil && Int(nextRange!.lowerBound) < f.number {
p.print("try visitor.\(visitExtensionsName)(fields: _protobuf_extensionFieldValues, start: \(nextRange!.lowerBound), end: \(nextRange!.upperBound))")
nextRange = ranges.next()
}
f.generateTraverse(printer: &p)
}
while nextRange != nil {
p.print("try visitor.\(visitExtensionsName)(fields: _protobuf_extensionFieldValues, start: \(nextRange!.lowerBound), end: \(nextRange!.upperBound))")
p.withIndentation { p in
for f in fieldsSortedByNumber {
while nextRange != nil && Int(nextRange!.lowerBound) < f.number {
p.print(".\(visitExtensionsName)({ $0._protobuf_extensionFieldValues }, start: \(nextRange!.lowerBound), end: \(nextRange!.upperBound)),")
nextRange = ranges.next()
}
f.generateFieldNode(printer: &p)
}
while nextRange != nil {
p.print(".\(visitExtensionsName)({ $0._protobuf_extensionFieldValues }, start: \(nextRange!.lowerBound), end: \(nextRange!.upperBound)),")
nextRange = ranges.next()
}
}
p.print("]")

for oneof in oneofs {
oneof.generateFieldNodeStaticLet(printer: &p)
}
}

private func generateTraverse(printer p: inout CodePrinter) {
// If we have an AnyMessageStorageClass we need to generate the traverse function so it can include a preTraverse call
guard let storage, storage is AnyMessageStorageClassGenerator else {
return
}
p.print("\(visibility)func traverse<V: Visitor>(visitor: inout V) throws {")
p.withIndentation { p in
p.print("try _storage.preTraverse()")
p.print("for field in Self._fields {")
p.printIndented("try field.traverse(message: self, visitor: &visitor)")
p.print("}")
p.print("try unknownFields.traverse(visitor: &visitor)")
}
p.print("}")
Expand Down
32 changes: 32 additions & 0 deletions Sources/protoc-gen-swift/OneofGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ class OneofGenerator {
func generateTraverse(printer p: inout CodePrinter) {
oneof.generateTraverse(printer: &p, field: self)
}

func generateFieldNode(printer p: inout SwiftProtobufPluginLibrary.CodePrinter) {
oneof.generateFieldNode(printer: &p, field: self)
}

}

private let oneofDescriptor: OneofDescriptor
Expand Down Expand Up @@ -360,6 +365,33 @@ class OneofGenerator {
}

var generateTraverseUsesLocals: Bool { return true }

func generateFieldNode(printer p: inout CodePrinter, field: MemberFieldGenerator) {
// First field in the group causes the output.
let group = fieldSortedGrouped[field.group]
guard field === group.first else { return }
p.print(".oneOf({ $0.\(swiftFieldName) }) {")
p.withIndentation { p in
p.print("switch $0 {")
for field in group {
p.print("case \(field.dottedSwiftName):")
p.printIndented("return _oneOfField_\(field.swiftName)")
}
if group.count != fields.count {
p.print("default:")
p.printIndented("return nil")
}
p.print("}")
}
p.print("},")
}

func generateFieldNodeStaticLet(printer p: inout CodePrinter) {
for field in fieldsSortedByNumber {
p.print("private static let _oneOfField_\(field.swiftName): Field<Self> = .singular\(field.protoGenericType)({ $0.\(field.swiftName) }, fieldNumber: \(field.number), isUnset: { _ in false })")
}

}

func generateTraverse(printer p: inout CodePrinter, field: MemberFieldGenerator) {
// First field in the group causes the output.
Expand Down

0 comments on commit 5de597d

Please sign in to comment.