Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ public enum ProtoIntEncoding {
case signed
case variable
}

extension ProtoIntEncoding : Sendable {
}
31 changes: 31 additions & 0 deletions wire-runtime-swift/src/test/swift/ExtensibleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,35 @@ final class ExtensibleTests: XCTestCase {
XCTAssertEqual(LargeExtensible.default_ext_value17, "my extension default value")
XCTAssertEqual(LargeExtensible.default_ext_value18, "")
}

func testExtensionFieldNumberAndEncodingConstants() {
XCTAssertEqual(Extensible.fieldNumber_ext_int32, 1001)
XCTAssertEqual(Extensible.fieldEncoding_ext_int32, .variable)
XCTAssertEqual(Extensible.fieldNumber_ext_sint32, 1003)
XCTAssertEqual(Extensible.fieldEncoding_ext_sint32, .signed)
XCTAssertEqual(Extensible.fieldNumber_ext_fixed32, 1004)
XCTAssertEqual(Extensible.fieldEncoding_ext_fixed32, .fixed)
XCTAssertEqual(Extensible.fieldNumber_ext_string, 1014)
XCTAssertEqual(LargeExtensible.fieldNumber_ext_value17, 17)
XCTAssertEqual(LargeExtensible.fieldNumber_rep_ext_sint32, 21)
XCTAssertEqual(LargeExtensible.fieldEncoding_rep_ext_sint32, .signed)

// Round-trip a value through the raw APIs using only generated constants.
var message = Extensible()
message.setUnknownField(
fieldNumber: Extensible.fieldNumber_ext_sint32,
newValue: Int32(-42),
encoding: Extensible.fieldEncoding_ext_sint32
)
XCTAssertEqual(
message.parseUnknownField(
fieldNumber: Extensible.fieldNumber_ext_sint32,
type: Int32.self,
encoding: Extensible.fieldEncoding_ext_sint32
),
-42
)
// The generated accessor reads the same field the raw APIs just wrote.
XCTAssertEqual(message.ext_sint32, -42)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class SwiftGenerator private constructor(
private val unknownFields = DeclaredTypeName.typeName("Wire.UnknownFields")
private val extensibleUnknownFields = DeclaredTypeName.typeName("Wire.ExtensibleUnknownFields")
private val protoExtensible = DeclaredTypeName.typeName("Wire.ProtoExtensible")
private val protoIntEncoding = DeclaredTypeName.typeName("Wire.ProtoIntEncoding")

private val stringLiteralCodingKeys = DeclaredTypeName.typeName("Wire.StringLiteralCodingKeys")

Expand Down Expand Up @@ -1328,6 +1329,9 @@ class SwiftGenerator private constructor(

addProperty(defaultProperty)
}

addProperty(extensionFieldNumberProperty(field))
extensionFieldEncodingProperty(field)?.let { addProperty(it) }
}
}
.build()
Expand All @@ -1337,6 +1341,23 @@ class SwiftGenerator private constructor(
}
}

private fun extensionFieldNumberProperty(field: Field): PropertySpec = PropertySpec.varBuilder("fieldNumber_${field.safeName}", UINT32, PUBLIC, STATIC)
.addDoc("Field number for the %L extension field.\n", field.safeName)
.mutable(false)
.initializer("%L", field.tag)
.build()

// Emitted whenever the accessor passes `encoding:` to parseUnknownField/setUnknownField, so
// callers of those APIs can source the (fieldNumber, encoding) pair entirely from codegen.
private fun extensionFieldEncodingProperty(field: Field): PropertySpec? {
val encoding = field.type!!.encoding ?: return null
return PropertySpec.varBuilder("fieldEncoding_${field.safeName}", protoIntEncoding, PUBLIC, STATIC)
.addDoc("Integer encoding for the %L extension field.\n", field.safeName)
.mutable(false)
.initializer(".%N", encoding)
.build()
}

private fun generateMessageExtensions(
type: MessageType,
structType: DeclaredTypeName,
Expand Down Expand Up @@ -1416,6 +1437,12 @@ class SwiftGenerator private constructor(

addProperty(defaultProperty)
}

// The constants belong on the extended type; the storage pass must skip them or they'd be declared twice.
if (!forStorageType) {
addProperty(extensionFieldNumberProperty(field))
extensionFieldEncodingProperty(field)?.let { addProperty(it) }
}
}
}
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.squareup.wire.swift
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.doesNotContain
import assertk.assertions.isEqualTo
import com.squareup.wire.buildSchema
import com.squareup.wire.schema.Schema
import io.outfoxx.swiftpoet.FileSpec
Expand Down Expand Up @@ -64,6 +65,119 @@ class SwiftGeneratorTest {
assertThat(code).contains("self.parseUnknownField(fieldNumber: 50003)")
}

@Test fun extensionFieldNumbersAreExposedAsConstants() {
val schema = buildSchema {
add(
"custom_options.proto".toPath(),
"""
|syntax = "proto3";
|
|package squareup.protos3.kotlin.custom_options;
|
|import "google/protobuf/descriptor.proto";
|
|extend google.protobuf.MessageOptions {
| string implicit_scalar = 50001;
| repeated string repeated_scalar = 50003;
|}
""".trimMargin(),
)
}

val code = schema.generateSwift("google.protobuf.MessageOptions")

assertThat(code).contains("public static let fieldNumber_implicit_scalar: UInt32 = 50001")
assertThat(code).contains("public static let fieldNumber_repeated_scalar: UInt32 = 50003")
}

@Test fun extensionFieldEncodingsAreExposedAsConstants() {
val schema = buildSchema {
add(
"extensible_message.proto".toPath(),
"""
|syntax = "proto2";
|
|package squareup.protos2.kotlin;
|
|message ExtensibleMessage {
| extensions 100 to 200;
|}
|
|extend ExtensibleMessage {
| optional int32 ext_int32 = 100;
| optional sint32 ext_sint32 = 101;
| optional fixed32 ext_fixed32 = 102;
| repeated sint64 rep_ext_sint64 = 103;
| optional string ext_string = 104;
|}
""".trimMargin(),
)
}

val code = schema.generateSwift("squareup.protos2.kotlin.ExtensibleMessage")

assertThat(code).contains("public static let fieldEncoding_ext_int32: ProtoIntEncoding = .variable")
assertThat(code).contains("public static let fieldEncoding_ext_sint32: ProtoIntEncoding = .signed")
assertThat(code).contains("public static let fieldEncoding_ext_fixed32: ProtoIntEncoding = .fixed")
assertThat(code).contains("public static let fieldEncoding_rep_ext_sint64: ProtoIntEncoding = .signed")
// Only integer fields take an explicit encoding in parseUnknownField/setUnknownField.
assertThat(code).doesNotContain("fieldEncoding_ext_string")
}

@Test fun extensionFieldNumberConstantsAreGeneratedOnceForHeapAllocatedMessages() {
val schema = buildSchema {
add(
"big_message.proto".toPath(),
"""
|syntax = "proto2";
|
|package squareup.protos2.kotlin;
|
|message BigMessage {
| optional int32 f1 = 1;
| optional int32 f2 = 2;
| optional int32 f3 = 3;
| optional int32 f4 = 4;
| optional int32 f5 = 5;
| optional int32 f6 = 6;
| optional int32 f7 = 7;
| optional int32 f8 = 8;
| optional int32 f9 = 9;
| optional int32 f10 = 10;
| optional int32 f11 = 11;
| optional int32 f12 = 12;
| optional int32 f13 = 13;
| optional int32 f14 = 14;
| optional int32 f15 = 15;
| optional int32 f16 = 16;
|
| extensions 1000 to 1999;
|}
|
|extend BigMessage {
| optional string extra = 1000;
| optional sint32 extra_signed = 1001;
|}
""".trimMargin(),
)
}

val code = schema.generateSwift("squareup.protos2.kotlin.BigMessage")

// Precondition: the message must actually be heap-allocated, or the single-emission
// assertion below passes trivially because only one extension block is generated at all.
assertThat(code).contains("public struct Storage")

val constant = "public static let fieldNumber_extra: UInt32 = 1000"
assertThat(code).contains(constant)
// The constant belongs on the extended type only, not on its CopyOnWrite storage type.
assertThat(code.indexOf(constant)).isEqualTo(code.lastIndexOf(constant))

val encodingConstant = "public static let fieldEncoding_extra_signed: ProtoIntEncoding = .signed"
assertThat(code).contains(encodingConstant)
assertThat(code.indexOf(encodingConstant)).isEqualTo(code.lastIndexOf(encodingConstant))
}

@Test fun usesFieldMask() {
val schema = buildSchema {
add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ extension SwiftModuleOneMessage {
* Default value for extension_message extension field.
*/
public static let default_extension_message: ExtensionMessage = .defaultedValue
/**
* Field number for the extension_message extension field.
*/
public static let fieldNumber_extension_message: UInt32 = 1000
}

#if !WIRE_REMOVE_EQUATABLE
Expand Down
Loading