From 28e112b53bab4137b04b6df5f592bc8598c29d82 Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Fri, 14 Aug 2026 12:46:58 -0400 Subject: [PATCH 1/7] Expose extension field numbers as generated Swift constants Wire's Swift codegen generates accessor properties for extension fields, but never exposes the underlying field number. Consumers that call parseUnknownField/setUnknownField directly - for example to supply a custom ProtoDecoder with a different enum decoding strategy, or to distinguish an absent extension from an undecodable one via an unknownFields presence check - must hard-code the field number by cross-referencing the .proto source, and nothing keeps those copies in sync with codegen. GPB exposes the same information through GPBExtensionDescriptor; Wire had no Swift equivalent. Generate a `public static let fieldNumber_: UInt32` constant alongside each extension accessor, mirroring the existing `default_` constants. The constant is emitted for every extension field (including repeated fields, which have no default constant), on the extended type only - not on its CopyOnWrite storage type. Co-Authored-By: Claude Fable 5 --- .../com/squareup/wire/swift/SwiftGenerator.kt | 12 ++ .../squareup/wire/swift/SwiftGeneratorTest.kt | 71 +++++++ .../module_one/SwiftModuleOneMessage.swift | 4 + .../no-manifest/src/main/swift/AllTypes.swift | 192 ++++++++++++++++++ .../no-manifest/src/main/swift/FooBar.swift | 12 ++ 5 files changed, 291 insertions(+) diff --git a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt index 45a6cb6cdf..42a470e705 100644 --- a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt +++ b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt @@ -1328,6 +1328,8 @@ class SwiftGenerator private constructor( addProperty(defaultProperty) } + + addProperty(extensionFieldNumberProperty(field)) } } .build() @@ -1337,6 +1339,12 @@ 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() + private fun generateMessageExtensions( type: MessageType, structType: DeclaredTypeName, @@ -1416,6 +1424,10 @@ class SwiftGenerator private constructor( addProperty(defaultProperty) } + + if (!forStorageType) { + addProperty(extensionFieldNumberProperty(field)) + } } } .build() diff --git a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt index ffbda72d89..2a35a2c431 100644 --- a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt +++ b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt @@ -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 @@ -64,6 +65,76 @@ 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 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; + |} + """.trimMargin(), + ) + } + + val code = schema.generateSwift("squareup.protos2.kotlin.BigMessage") + + 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)) + } + @Test fun usesFieldMask() { val schema = buildSchema { add( diff --git a/wire-tests-swift/manifest/module_one/SwiftModuleOneMessage.swift b/wire-tests-swift/manifest/module_one/SwiftModuleOneMessage.swift index 37cecfe690..72c0afe4c6 100644 --- a/wire-tests-swift/manifest/module_one/SwiftModuleOneMessage.swift +++ b/wire-tests-swift/manifest/module_one/SwiftModuleOneMessage.swift @@ -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 diff --git a/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift b/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift index f9fb25df59..9bce320552 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift @@ -1284,6 +1284,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_int32 extension field. */ public static let default_ext_opt_int32: Int32 = .defaultedValue + /** + * Field number for the ext_opt_int32 extension field. + */ + public static let fieldNumber_ext_opt_int32: UInt32 = 1001 /** * * Source: all_types.proto @@ -1300,6 +1304,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_uint32 extension field. */ public static let default_ext_opt_uint32: UInt32 = .defaultedValue + /** + * Field number for the ext_opt_uint32 extension field. + */ + public static let fieldNumber_ext_opt_uint32: UInt32 = 1002 /** * * Source: all_types.proto @@ -1316,6 +1324,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_sint32 extension field. */ public static let default_ext_opt_sint32: Int32 = .defaultedValue + /** + * Field number for the ext_opt_sint32 extension field. + */ + public static let fieldNumber_ext_opt_sint32: UInt32 = 1003 /** * * Source: all_types.proto @@ -1332,6 +1344,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_fixed32 extension field. */ public static let default_ext_opt_fixed32: UInt32 = .defaultedValue + /** + * Field number for the ext_opt_fixed32 extension field. + */ + public static let fieldNumber_ext_opt_fixed32: UInt32 = 1004 /** * * Source: all_types.proto @@ -1348,6 +1364,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_sfixed32 extension field. */ public static let default_ext_opt_sfixed32: Int32 = .defaultedValue + /** + * Field number for the ext_opt_sfixed32 extension field. + */ + public static let fieldNumber_ext_opt_sfixed32: UInt32 = 1005 /** * * Source: all_types.proto @@ -1364,6 +1384,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_int64 extension field. */ public static let default_ext_opt_int64: Int64 = .defaultedValue + /** + * Field number for the ext_opt_int64 extension field. + */ + public static let fieldNumber_ext_opt_int64: UInt32 = 1006 /** * * Source: all_types.proto @@ -1380,6 +1404,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_uint64 extension field. */ public static let default_ext_opt_uint64: UInt64 = .defaultedValue + /** + * Field number for the ext_opt_uint64 extension field. + */ + public static let fieldNumber_ext_opt_uint64: UInt32 = 1007 /** * * Source: all_types.proto @@ -1396,6 +1424,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_sint64 extension field. */ public static let default_ext_opt_sint64: Int64 = .defaultedValue + /** + * Field number for the ext_opt_sint64 extension field. + */ + public static let fieldNumber_ext_opt_sint64: UInt32 = 1008 /** * * Source: all_types.proto @@ -1412,6 +1444,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_fixed64 extension field. */ public static let default_ext_opt_fixed64: UInt64 = .defaultedValue + /** + * Field number for the ext_opt_fixed64 extension field. + */ + public static let fieldNumber_ext_opt_fixed64: UInt32 = 1009 /** * * Source: all_types.proto @@ -1428,6 +1464,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_sfixed64 extension field. */ public static let default_ext_opt_sfixed64: Int64 = .defaultedValue + /** + * Field number for the ext_opt_sfixed64 extension field. + */ + public static let fieldNumber_ext_opt_sfixed64: UInt32 = 1010 /** * * Source: all_types.proto @@ -1444,6 +1484,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_bool extension field. */ public static let default_ext_opt_bool: Bool = .defaultedValue + /** + * Field number for the ext_opt_bool extension field. + */ + public static let fieldNumber_ext_opt_bool: UInt32 = 1011 /** * * Source: all_types.proto @@ -1460,6 +1504,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_float extension field. */ public static let default_ext_opt_float: Float = .defaultedValue + /** + * Field number for the ext_opt_float extension field. + */ + public static let fieldNumber_ext_opt_float: UInt32 = 1012 /** * * Source: all_types.proto @@ -1476,6 +1524,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_double extension field. */ public static let default_ext_opt_double: Double = .defaultedValue + /** + * Field number for the ext_opt_double extension field. + */ + public static let fieldNumber_ext_opt_double: UInt32 = 1013 /** * * Source: all_types.proto @@ -1492,6 +1544,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_string extension field. */ public static let default_ext_opt_string: String = .defaultedValue + /** + * Field number for the ext_opt_string extension field. + */ + public static let fieldNumber_ext_opt_string: UInt32 = 1014 /** * * Source: all_types.proto @@ -1508,6 +1564,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_bytes extension field. */ public static let default_ext_opt_bytes: Foundation.Data = .defaultedValue + /** + * Field number for the ext_opt_bytes extension field. + */ + public static let fieldNumber_ext_opt_bytes: UInt32 = 1015 /** * * Source: all_types.proto @@ -1520,6 +1580,10 @@ extension AllTypes : ProtoExtensible { storage.ext_opt_nested_enum = newValue } } + /** + * Field number for the ext_opt_nested_enum extension field. + */ + public static let fieldNumber_ext_opt_nested_enum: UInt32 = 1016 /** * * Source: all_types.proto @@ -1536,6 +1600,10 @@ extension AllTypes : ProtoExtensible { * Default value for ext_opt_nested_message extension field. */ public static let default_ext_opt_nested_message: AllTypes.NestedMessage = .defaultedValue + /** + * Field number for the ext_opt_nested_message extension field. + */ + public static let fieldNumber_ext_opt_nested_message: UInt32 = 1017 /** * * Source: all_types.proto @@ -1548,6 +1616,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_int32 = newValue } } + /** + * Field number for the ext_rep_int32 extension field. + */ + public static let fieldNumber_ext_rep_int32: UInt32 = 1101 /** * * Source: all_types.proto @@ -1560,6 +1632,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_uint32 = newValue } } + /** + * Field number for the ext_rep_uint32 extension field. + */ + public static let fieldNumber_ext_rep_uint32: UInt32 = 1102 /** * * Source: all_types.proto @@ -1572,6 +1648,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_sint32 = newValue } } + /** + * Field number for the ext_rep_sint32 extension field. + */ + public static let fieldNumber_ext_rep_sint32: UInt32 = 1103 /** * * Source: all_types.proto @@ -1584,6 +1664,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_fixed32 = newValue } } + /** + * Field number for the ext_rep_fixed32 extension field. + */ + public static let fieldNumber_ext_rep_fixed32: UInt32 = 1104 /** * * Source: all_types.proto @@ -1596,6 +1680,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_sfixed32 = newValue } } + /** + * Field number for the ext_rep_sfixed32 extension field. + */ + public static let fieldNumber_ext_rep_sfixed32: UInt32 = 1105 /** * * Source: all_types.proto @@ -1608,6 +1696,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_int64 = newValue } } + /** + * Field number for the ext_rep_int64 extension field. + */ + public static let fieldNumber_ext_rep_int64: UInt32 = 1106 /** * * Source: all_types.proto @@ -1620,6 +1712,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_uint64 = newValue } } + /** + * Field number for the ext_rep_uint64 extension field. + */ + public static let fieldNumber_ext_rep_uint64: UInt32 = 1107 /** * * Source: all_types.proto @@ -1632,6 +1728,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_sint64 = newValue } } + /** + * Field number for the ext_rep_sint64 extension field. + */ + public static let fieldNumber_ext_rep_sint64: UInt32 = 1108 /** * * Source: all_types.proto @@ -1644,6 +1744,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_fixed64 = newValue } } + /** + * Field number for the ext_rep_fixed64 extension field. + */ + public static let fieldNumber_ext_rep_fixed64: UInt32 = 1109 /** * * Source: all_types.proto @@ -1656,6 +1760,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_sfixed64 = newValue } } + /** + * Field number for the ext_rep_sfixed64 extension field. + */ + public static let fieldNumber_ext_rep_sfixed64: UInt32 = 1110 /** * * Source: all_types.proto @@ -1668,6 +1776,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_bool = newValue } } + /** + * Field number for the ext_rep_bool extension field. + */ + public static let fieldNumber_ext_rep_bool: UInt32 = 1111 /** * * Source: all_types.proto @@ -1680,6 +1792,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_float = newValue } } + /** + * Field number for the ext_rep_float extension field. + */ + public static let fieldNumber_ext_rep_float: UInt32 = 1112 /** * * Source: all_types.proto @@ -1692,6 +1808,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_double = newValue } } + /** + * Field number for the ext_rep_double extension field. + */ + public static let fieldNumber_ext_rep_double: UInt32 = 1113 /** * * Source: all_types.proto @@ -1704,6 +1824,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_string = newValue } } + /** + * Field number for the ext_rep_string extension field. + */ + public static let fieldNumber_ext_rep_string: UInt32 = 1114 /** * * Source: all_types.proto @@ -1716,6 +1840,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_bytes = newValue } } + /** + * Field number for the ext_rep_bytes extension field. + */ + public static let fieldNumber_ext_rep_bytes: UInt32 = 1115 /** * * Source: all_types.proto @@ -1728,6 +1856,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_nested_enum = newValue } } + /** + * Field number for the ext_rep_nested_enum extension field. + */ + public static let fieldNumber_ext_rep_nested_enum: UInt32 = 1116 /** * * Source: all_types.proto @@ -1740,6 +1872,10 @@ extension AllTypes : ProtoExtensible { storage.ext_rep_nested_message = newValue } } + /** + * Field number for the ext_rep_nested_message extension field. + */ + public static let fieldNumber_ext_rep_nested_message: UInt32 = 1117 /** * * Source: all_types.proto @@ -1752,6 +1888,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_int32 = newValue } } + /** + * Field number for the ext_pack_int32 extension field. + */ + public static let fieldNumber_ext_pack_int32: UInt32 = 1201 /** * * Source: all_types.proto @@ -1764,6 +1904,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_uint32 = newValue } } + /** + * Field number for the ext_pack_uint32 extension field. + */ + public static let fieldNumber_ext_pack_uint32: UInt32 = 1202 /** * * Source: all_types.proto @@ -1776,6 +1920,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_sint32 = newValue } } + /** + * Field number for the ext_pack_sint32 extension field. + */ + public static let fieldNumber_ext_pack_sint32: UInt32 = 1203 /** * * Source: all_types.proto @@ -1788,6 +1936,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_fixed32 = newValue } } + /** + * Field number for the ext_pack_fixed32 extension field. + */ + public static let fieldNumber_ext_pack_fixed32: UInt32 = 1204 /** * * Source: all_types.proto @@ -1800,6 +1952,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_sfixed32 = newValue } } + /** + * Field number for the ext_pack_sfixed32 extension field. + */ + public static let fieldNumber_ext_pack_sfixed32: UInt32 = 1205 /** * * Source: all_types.proto @@ -1812,6 +1968,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_int64 = newValue } } + /** + * Field number for the ext_pack_int64 extension field. + */ + public static let fieldNumber_ext_pack_int64: UInt32 = 1206 /** * * Source: all_types.proto @@ -1824,6 +1984,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_uint64 = newValue } } + /** + * Field number for the ext_pack_uint64 extension field. + */ + public static let fieldNumber_ext_pack_uint64: UInt32 = 1207 /** * * Source: all_types.proto @@ -1836,6 +2000,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_sint64 = newValue } } + /** + * Field number for the ext_pack_sint64 extension field. + */ + public static let fieldNumber_ext_pack_sint64: UInt32 = 1208 /** * * Source: all_types.proto @@ -1848,6 +2016,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_fixed64 = newValue } } + /** + * Field number for the ext_pack_fixed64 extension field. + */ + public static let fieldNumber_ext_pack_fixed64: UInt32 = 1209 /** * * Source: all_types.proto @@ -1860,6 +2032,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_sfixed64 = newValue } } + /** + * Field number for the ext_pack_sfixed64 extension field. + */ + public static let fieldNumber_ext_pack_sfixed64: UInt32 = 1210 /** * * Source: all_types.proto @@ -1872,6 +2048,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_bool = newValue } } + /** + * Field number for the ext_pack_bool extension field. + */ + public static let fieldNumber_ext_pack_bool: UInt32 = 1211 /** * * Source: all_types.proto @@ -1884,6 +2064,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_float = newValue } } + /** + * Field number for the ext_pack_float extension field. + */ + public static let fieldNumber_ext_pack_float: UInt32 = 1212 /** * * Source: all_types.proto @@ -1896,6 +2080,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_double = newValue } } + /** + * Field number for the ext_pack_double extension field. + */ + public static let fieldNumber_ext_pack_double: UInt32 = 1213 /** * * Source: all_types.proto @@ -1908,6 +2096,10 @@ extension AllTypes : ProtoExtensible { storage.ext_pack_nested_enum = newValue } } + /** + * Field number for the ext_pack_nested_enum extension field. + */ + public static let fieldNumber_ext_pack_nested_enum: UInt32 = 1216 } #if !WIRE_REMOVE_EQUATABLE diff --git a/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift b/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift index 61b1ee177e..95efa418f6 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift @@ -45,6 +45,10 @@ extension FooBar { self.setUnknownField(fieldNumber: 101, newValue: newValue) } } + /** + * Field number for the ext extension field. + */ + public static let fieldNumber_ext: UInt32 = 101 /** * * Source: custom_options.proto @@ -57,6 +61,10 @@ extension FooBar { self.setUnknownField(fieldNumber: 102, newValue: newValue) } } + /** + * Field number for the rep extension field. + */ + public static let fieldNumber_rep: UInt32 = 102 /** * * Source: custom_options.proto @@ -73,6 +81,10 @@ extension FooBar { * Default value for more_string extension field. */ public static let default_more_string: String = .defaultedValue + /** + * Field number for the more_string extension field. + */ + public static let fieldNumber_more_string: UInt32 = 150 } #if !WIRE_REMOVE_EQUATABLE From 037f1386599919a7d3bc192be55a8ae6769d8f1b Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 09:55:44 -0400 Subject: [PATCH 2/7] Emit extension field encodings alongside field numbers The fieldNumber_ constants cover only half of what the raw parseUnknownField/setUnknownField APIs need: integer extension fields also require an explicit `encoding:` argument, which callers previously had to copy out of the .proto source by hand - and a wrong copy silently decodes wrong instead of failing to compile. Generate a `public static let fieldEncoding_: ProtoIntEncoding` constant next to the field number whenever the generated accessor passes `encoding:` (signed, fixed, and variable integer fields, both singular and repeated), so the (fieldNumber, encoding) pair travels together and both come from codegen. Like the field number, the encoding constant is emitted on the extended type only, covered by an integer extension field in the heap-allocation single-emission test. Addresses review feedback from oldergod on #3676. Co-Authored-By: Claude Fable 5 --- .../com/squareup/wire/swift/SwiftGenerator.kt | 14 ++ .../squareup/wire/swift/SwiftGeneratorTest.kt | 39 ++++++ .../no-manifest/src/main/swift/AllTypes.swift | 120 ++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt index 42a470e705..020893b851 100644 --- a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt +++ b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt @@ -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") @@ -1330,6 +1331,7 @@ class SwiftGenerator private constructor( } addProperty(extensionFieldNumberProperty(field)) + extensionFieldEncodingProperty(field)?.let { addProperty(it) } } } .build() @@ -1345,6 +1347,17 @@ class SwiftGenerator private constructor( .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, @@ -1427,6 +1440,7 @@ class SwiftGenerator private constructor( if (!forStorageType) { addProperty(extensionFieldNumberProperty(field)) + extensionFieldEncodingProperty(field)?.let { addProperty(it) } } } } diff --git a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt index 2a35a2c431..17a8420cdc 100644 --- a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt +++ b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt @@ -90,6 +90,40 @@ class SwiftGeneratorTest { 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( @@ -122,6 +156,7 @@ class SwiftGeneratorTest { | |extend BigMessage { | optional string extra = 1000; + | optional sint32 extra_signed = 1001; |} """.trimMargin(), ) @@ -133,6 +168,10 @@ class SwiftGeneratorTest { 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() { diff --git a/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift b/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift index 9bce320552..a5cefed926 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift @@ -1288,6 +1288,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_int32 extension field. */ public static let fieldNumber_ext_opt_int32: UInt32 = 1001 + /** + * Integer encoding for the ext_opt_int32 extension field. + */ + public static let fieldEncoding_ext_opt_int32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1308,6 +1312,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_uint32 extension field. */ public static let fieldNumber_ext_opt_uint32: UInt32 = 1002 + /** + * Integer encoding for the ext_opt_uint32 extension field. + */ + public static let fieldEncoding_ext_opt_uint32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1328,6 +1336,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_sint32 extension field. */ public static let fieldNumber_ext_opt_sint32: UInt32 = 1003 + /** + * Integer encoding for the ext_opt_sint32 extension field. + */ + public static let fieldEncoding_ext_opt_sint32: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -1348,6 +1360,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_fixed32 extension field. */ public static let fieldNumber_ext_opt_fixed32: UInt32 = 1004 + /** + * Integer encoding for the ext_opt_fixed32 extension field. + */ + public static let fieldEncoding_ext_opt_fixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1368,6 +1384,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_sfixed32 extension field. */ public static let fieldNumber_ext_opt_sfixed32: UInt32 = 1005 + /** + * Integer encoding for the ext_opt_sfixed32 extension field. + */ + public static let fieldEncoding_ext_opt_sfixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1388,6 +1408,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_int64 extension field. */ public static let fieldNumber_ext_opt_int64: UInt32 = 1006 + /** + * Integer encoding for the ext_opt_int64 extension field. + */ + public static let fieldEncoding_ext_opt_int64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1408,6 +1432,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_uint64 extension field. */ public static let fieldNumber_ext_opt_uint64: UInt32 = 1007 + /** + * Integer encoding for the ext_opt_uint64 extension field. + */ + public static let fieldEncoding_ext_opt_uint64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1428,6 +1456,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_sint64 extension field. */ public static let fieldNumber_ext_opt_sint64: UInt32 = 1008 + /** + * Integer encoding for the ext_opt_sint64 extension field. + */ + public static let fieldEncoding_ext_opt_sint64: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -1448,6 +1480,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_fixed64 extension field. */ public static let fieldNumber_ext_opt_fixed64: UInt32 = 1009 + /** + * Integer encoding for the ext_opt_fixed64 extension field. + */ + public static let fieldEncoding_ext_opt_fixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1468,6 +1504,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_opt_sfixed64 extension field. */ public static let fieldNumber_ext_opt_sfixed64: UInt32 = 1010 + /** + * Integer encoding for the ext_opt_sfixed64 extension field. + */ + public static let fieldEncoding_ext_opt_sfixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1620,6 +1660,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_int32 extension field. */ public static let fieldNumber_ext_rep_int32: UInt32 = 1101 + /** + * Integer encoding for the ext_rep_int32 extension field. + */ + public static let fieldEncoding_ext_rep_int32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1636,6 +1680,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_uint32 extension field. */ public static let fieldNumber_ext_rep_uint32: UInt32 = 1102 + /** + * Integer encoding for the ext_rep_uint32 extension field. + */ + public static let fieldEncoding_ext_rep_uint32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1652,6 +1700,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_sint32 extension field. */ public static let fieldNumber_ext_rep_sint32: UInt32 = 1103 + /** + * Integer encoding for the ext_rep_sint32 extension field. + */ + public static let fieldEncoding_ext_rep_sint32: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -1668,6 +1720,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_fixed32 extension field. */ public static let fieldNumber_ext_rep_fixed32: UInt32 = 1104 + /** + * Integer encoding for the ext_rep_fixed32 extension field. + */ + public static let fieldEncoding_ext_rep_fixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1684,6 +1740,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_sfixed32 extension field. */ public static let fieldNumber_ext_rep_sfixed32: UInt32 = 1105 + /** + * Integer encoding for the ext_rep_sfixed32 extension field. + */ + public static let fieldEncoding_ext_rep_sfixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1700,6 +1760,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_int64 extension field. */ public static let fieldNumber_ext_rep_int64: UInt32 = 1106 + /** + * Integer encoding for the ext_rep_int64 extension field. + */ + public static let fieldEncoding_ext_rep_int64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1716,6 +1780,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_uint64 extension field. */ public static let fieldNumber_ext_rep_uint64: UInt32 = 1107 + /** + * Integer encoding for the ext_rep_uint64 extension field. + */ + public static let fieldEncoding_ext_rep_uint64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1732,6 +1800,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_sint64 extension field. */ public static let fieldNumber_ext_rep_sint64: UInt32 = 1108 + /** + * Integer encoding for the ext_rep_sint64 extension field. + */ + public static let fieldEncoding_ext_rep_sint64: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -1748,6 +1820,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_fixed64 extension field. */ public static let fieldNumber_ext_rep_fixed64: UInt32 = 1109 + /** + * Integer encoding for the ext_rep_fixed64 extension field. + */ + public static let fieldEncoding_ext_rep_fixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1764,6 +1840,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_rep_sfixed64 extension field. */ public static let fieldNumber_ext_rep_sfixed64: UInt32 = 1110 + /** + * Integer encoding for the ext_rep_sfixed64 extension field. + */ + public static let fieldEncoding_ext_rep_sfixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1892,6 +1972,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_int32 extension field. */ public static let fieldNumber_ext_pack_int32: UInt32 = 1201 + /** + * Integer encoding for the ext_pack_int32 extension field. + */ + public static let fieldEncoding_ext_pack_int32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1908,6 +1992,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_uint32 extension field. */ public static let fieldNumber_ext_pack_uint32: UInt32 = 1202 + /** + * Integer encoding for the ext_pack_uint32 extension field. + */ + public static let fieldEncoding_ext_pack_uint32: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1924,6 +2012,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_sint32 extension field. */ public static let fieldNumber_ext_pack_sint32: UInt32 = 1203 + /** + * Integer encoding for the ext_pack_sint32 extension field. + */ + public static let fieldEncoding_ext_pack_sint32: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -1940,6 +2032,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_fixed32 extension field. */ public static let fieldNumber_ext_pack_fixed32: UInt32 = 1204 + /** + * Integer encoding for the ext_pack_fixed32 extension field. + */ + public static let fieldEncoding_ext_pack_fixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1956,6 +2052,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_sfixed32 extension field. */ public static let fieldNumber_ext_pack_sfixed32: UInt32 = 1205 + /** + * Integer encoding for the ext_pack_sfixed32 extension field. + */ + public static let fieldEncoding_ext_pack_sfixed32: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -1972,6 +2072,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_int64 extension field. */ public static let fieldNumber_ext_pack_int64: UInt32 = 1206 + /** + * Integer encoding for the ext_pack_int64 extension field. + */ + public static let fieldEncoding_ext_pack_int64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -1988,6 +2092,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_uint64 extension field. */ public static let fieldNumber_ext_pack_uint64: UInt32 = 1207 + /** + * Integer encoding for the ext_pack_uint64 extension field. + */ + public static let fieldEncoding_ext_pack_uint64: ProtoIntEncoding = .variable /** * * Source: all_types.proto @@ -2004,6 +2112,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_sint64 extension field. */ public static let fieldNumber_ext_pack_sint64: UInt32 = 1208 + /** + * Integer encoding for the ext_pack_sint64 extension field. + */ + public static let fieldEncoding_ext_pack_sint64: ProtoIntEncoding = .signed /** * * Source: all_types.proto @@ -2020,6 +2132,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_fixed64 extension field. */ public static let fieldNumber_ext_pack_fixed64: UInt32 = 1209 + /** + * Integer encoding for the ext_pack_fixed64 extension field. + */ + public static let fieldEncoding_ext_pack_fixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto @@ -2036,6 +2152,10 @@ extension AllTypes : ProtoExtensible { * Field number for the ext_pack_sfixed64 extension field. */ public static let fieldNumber_ext_pack_sfixed64: UInt32 = 1210 + /** + * Integer encoding for the ext_pack_sfixed64 extension field. + */ + public static let fieldEncoding_ext_pack_sfixed64: ProtoIntEncoding = .fixed /** * * Source: all_types.proto From 16ee8f7d01a7e27fb1ae523b786768e81dffb5a1 Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 09:56:18 -0400 Subject: [PATCH 3/7] Assert heap allocation before testing single constant emission The heap-allocation test builds a message with exactly 16 fields - the heap-allocation threshold - and then asserts the extension constant is emitted only once. If the threshold moves or a field is dropped, only one extension block is generated at all and the single-emission assertion passes trivially while the test still reads as covering the storage-type guard. Assert the storage split actually happened as a precondition. Addresses review feedback from oldergod on #3676. Co-Authored-By: Claude Fable 5 --- .../test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt index 17a8420cdc..56c0006834 100644 --- a/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt +++ b/wire-swift-generator/src/test/java/com/squareup/wire/swift/SwiftGeneratorTest.kt @@ -164,6 +164,10 @@ class SwiftGeneratorTest { 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. From d4acc8a616d2d05df02c142ae6defe80b3cebc96 Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 09:57:13 -0400 Subject: [PATCH 4/7] Test extension constants against real generated types The default_ constants have a Swift-side test that reads them off real generated types; the fieldNumber_ and fieldEncoding_ constants only had generator-level string assertions. Read the constants off Extensible and LargeExtensible, and round-trip a value through setUnknownField/parseUnknownField using only generated constants, confirming the generated accessor observes what the raw APIs wrote. Addresses review feedback from oldergod on #3676. Co-Authored-By: Claude Fable 5 --- .../src/test/swift/ExtensibleTests.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/wire-runtime-swift/src/test/swift/ExtensibleTests.swift b/wire-runtime-swift/src/test/swift/ExtensibleTests.swift index a6f22140fe..a84f5c12f5 100644 --- a/wire-runtime-swift/src/test/swift/ExtensibleTests.swift +++ b/wire-runtime-swift/src/test/swift/ExtensibleTests.swift @@ -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) + } } From 2a4a0f29ead089c2fb45ff0a58be9432d3be79ff Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 09:57:53 -0400 Subject: [PATCH 5/7] Document why storage types skip extension constants The !forStorageType guard is the only thing preventing a duplicate declaration when both extension emission paths run over the same fields of a heap-allocated message, and that invariant was documented nowhere but a test. Addresses review feedback from oldergod on #3676. Co-Authored-By: Claude Fable 5 --- .../src/main/java/com/squareup/wire/swift/SwiftGenerator.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt index 020893b851..a22d5c3401 100644 --- a/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt +++ b/wire-swift-generator/src/main/java/com/squareup/wire/swift/SwiftGenerator.kt @@ -1438,6 +1438,7 @@ 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) } From e68a19a0e2bba7c72384d92b35d0d44d757c8dfd Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 11:05:37 -0400 Subject: [PATCH 6/7] Declare ProtoIntEncoding Sendable The new generated fieldEncoding_ constants are public static lets of type ProtoIntEncoding. Public types get no implicit Sendable conformance across module boundaries, so under strict-concurrency checking the generated code fails to compile: "static property is not concurrency-safe because non-'Sendable' type 'ProtoIntEncoding' may have shared mutable state". Declare the conformance explicitly - the enum is a payload-free value type. Caught by the swift CI job's golden compile step; the existing fieldNumber_ and default_ constants never hit this because UInt32 and the scalar default types are already Sendable. Co-Authored-By: Claude Fable 5 --- .../src/main/swift/ProtoCodable/ProtoIntCodable.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoIntCodable.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoIntCodable.swift index afe853c442..62ef82aee8 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoIntCodable.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoIntCodable.swift @@ -52,3 +52,6 @@ public enum ProtoIntEncoding { case signed case variable } + +extension ProtoIntEncoding : Sendable { +} From a3492545af0f2566e641839b026b1ff26f08262b Mon Sep 17 00:00:00 2001 From: Logan Blevins Date: Mon, 17 Aug 2026 11:49:58 -0400 Subject: [PATCH 7/7] Retrigger CI after runner infrastructure failures The swift and android (24) jobs died during runner setup and android (21) hit an adb daemon flake; none reached the code. Fork contributors cannot rerun failed jobs upstream, so an empty commit retriggers the workflow. Co-Authored-By: Claude Fable 5