From 8c68d05d48d782a28c6c6574579584572a9d5e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Quenaudon?= Date: Mon, 13 Jul 2026 14:40:19 +0100 Subject: [PATCH] FieldMask: finish him - Preserved Swift recursion depth during merged decoding. - Correctly merged repeated same-member message oneofs across Java, Kotlin, Swift, and dynamic adapters. - Deferred Swift proto2 required-field validation until all occurrences are merged. - Made FieldMask merging linear on JVM and Swift generated paths. --- .../wire/proto3/java/all_types/AllTypes.java | 14 +- .../com/squareup/wire/java/JavaGenerator.java | 103 ++++++++++----- .../squareup/wire/java/JavaGeneratorTest.java | 7 + .../squareup/wire/kotlin/KotlinGenerator.kt | 37 +++++- .../wire/kotlin/KotlinGeneratorTest.kt | 7 + .../swift/ProtoCodable/ProtoDecoder.swift | 22 ++- .../main/swift/ProtoCodable/ProtoReader.swift | 46 +++++-- .../src/test/proto/field_mask_message.proto | 8 ++ .../src/test/proto/simple2.proto | 9 ++ .../src/test/swift/FieldMaskTests.swift | 22 +++ .../src/test/swift/ProtoDecoderTests.swift | 40 ++++++ .../kotlin/com/squareup/wire/AnyMessage.kt | 5 + .../kotlin/com/squareup/wire/FieldMask.kt | 43 +++++- .../com/squareup/wire/internal/Internal.kt | 9 ++ .../wire/internal/RuntimeMessageAdapter.kt | 3 +- .../wire/schema/SchemaProtoAdapterFactory.kt | 1 + .../wire/schema/DynamicSerializationTest.kt | 54 ++++++++ .../com/squareup/wire/swift/SwiftGenerator.kt | 125 +++++++++++++++--- .../squareup/wire/swift/SwiftGeneratorTest.kt | 17 ++- .../src/main/swift/ContainsDuration.swift | 7 +- .../src/main/swift/ContainsTimestamp.swift | 7 +- .../SwiftModuleOneAmbiguousMessage.swift | 13 +- .../manifest/module_three/DateTime.swift | 7 +- .../module_two/SwiftModuleTwoMessage.swift | 7 +- .../no-manifest/src/main/swift/AllTypes.swift | 18 ++- .../no-manifest/src/main/swift/FooBar.swift | 7 +- .../no-manifest/src/main/swift/Form.swift | 74 +++++++++-- .../src/main/swift/OuterMessage.swift | 7 +- .../no-manifest/src/main/swift/Screen.swift | 27 +++- .../src/main/swift/VersionOne.swift | 7 +- .../src/main/swift/VersionTwo.swift | 7 +- 31 files changed, 643 insertions(+), 117 deletions(-) diff --git a/wire-golden-files/src/main/java/com/squareup/wire/proto3/java/all_types/AllTypes.java b/wire-golden-files/src/main/java/com/squareup/wire/proto3/java/all_types/AllTypes.java index eefe0b0e30..c0d31fd124 100644 --- a/wire-golden-files/src/main/java/com/squareup/wire/proto3/java/all_types/AllTypes.java +++ b/wire-golden-files/src/main/java/com/squareup/wire/proto3/java/all_types/AllTypes.java @@ -3002,13 +3002,13 @@ public AllTypes decode(ProtoReader reader) throws IOException { case 525: builder.map_int32_timestamp.putAll(map_int32_timestampAdapter().decode(reader)); break; case 601: builder.oneof_string(ProtoAdapter.STRING.decode(reader)); break; case 602: builder.oneof_int32(ProtoAdapter.INT32.decode(reader)); break; - case 603: builder.oneof_nested_message(NestedMessage.ADAPTER.decode(reader)); break; - case 618: builder.oneof_any(AnyMessage.ADAPTER.decode(reader)); break; - case 619: builder.oneof_duration(ProtoAdapter.DURATION.decode(reader)); break; - case 620: builder.oneof_struct(ProtoAdapter.STRUCT_MAP.decode(reader)); break; - case 621: builder.oneof_list_value(ProtoAdapter.STRUCT_LIST.decode(reader)); break; - case 624: builder.oneof_empty(ProtoAdapter.EMPTY.decode(reader)); break; - case 625: builder.oneof_timestamp(ProtoAdapter.INSTANT.decode(reader)); break; + case 603: builder.oneof_nested_message(Internal.decodeMessageOrMerge(NestedMessage.ADAPTER, reader, builder.oneof_nested_message)); break; + case 618: builder.oneof_any(Internal.decodeMessageOrMerge(AnyMessage.ADAPTER, reader, builder.oneof_any)); break; + case 619: builder.oneof_duration(Internal.decodeMessageOrMerge(ProtoAdapter.DURATION, reader, builder.oneof_duration)); break; + case 620: builder.oneof_struct(Internal.decodeMessageOrMerge(ProtoAdapter.STRUCT_MAP, reader, builder.oneof_struct)); break; + case 621: builder.oneof_list_value(Internal.decodeMessageOrMerge(ProtoAdapter.STRUCT_LIST, reader, builder.oneof_list_value)); break; + case 624: builder.oneof_empty(Internal.decodeMessageOrMerge(ProtoAdapter.EMPTY, reader, builder.oneof_empty)); break; + case 625: builder.oneof_timestamp(Internal.decodeMessageOrMerge(ProtoAdapter.INSTANT, reader, builder.oneof_timestamp)); break; default: { reader.readUnknownField(tag); } diff --git a/wire-java-generator/src/main/java/com/squareup/wire/java/JavaGenerator.java b/wire-java-generator/src/main/java/com/squareup/wire/java/JavaGenerator.java index f7f8175811..1b994a21a6 100644 --- a/wire-java-generator/src/main/java/com/squareup/wire/java/JavaGenerator.java +++ b/wire-java-generator/src/main/java/com/squareup/wire/java/JavaGenerator.java @@ -1347,7 +1347,7 @@ private MethodSpec messageAdapterDecode( if (isEnum(field.getType()) && !field.getType().equals(ProtoType.STRUCT_NULL)) { result.beginControlFlow("case $L:", fieldTag); result.beginControlFlow("try"); - result.addCode(decodeAndAssign(field, nameAllocator, useBuilder)); + result.addCode(decodeAndAssign(type, field, nameAllocator, useBuilder)); result.addCode(";\n"); if (useBuilder) { result.nextControlFlow("catch ($T e)", EnumConstantNotFoundException.class); @@ -1362,7 +1362,9 @@ private MethodSpec messageAdapterDecode( result.endControlFlow(); // case } else { result.addCode( - "case $L: $L; break;\n", fieldTag, decodeAndAssign(field, nameAllocator, useBuilder)); + "case $L: $L; break;\n", + fieldTag, + decodeAndAssign(type, field, nameAllocator, useBuilder)); } } @@ -1398,46 +1400,81 @@ private MethodSpec messageAdapterDecode( return result.build(); } - private CodeBlock decodeAndAssign(Field field, NameAllocator nameAllocator, boolean useBuilder) { + private CodeBlock decodeAndAssign( + MessageType message, Field field, NameAllocator nameAllocator, boolean useBuilder) { String fieldName = nameAllocator.get(field); CodeBlock decode = CodeBlock.of("$L.decode(reader)", singleAdapterFor(field, nameAllocator)); + CodeBlock assignment; if (field.isPacked()) { CodeBlock adapter = singleAdapterFor(field, nameAllocator); - return useBuilder - ? CodeBlock.of("$L.tryDecode(reader, builder.$L)", adapter, fieldName) - : CodeBlock.of("$L.tryDecode(reader, $L)", adapter, fieldName); + assignment = + useBuilder + ? CodeBlock.of("$L.tryDecode(reader, builder.$L)", adapter, fieldName) + : CodeBlock.of("$L.tryDecode(reader, $L)", adapter, fieldName); } else if (field.isRepeated()) { - return useBuilder - ? field.getType().equals(ProtoType.STRUCT_NULL) - ? CodeBlock.of("builder.$L.add(($T) $L)", fieldName, Void.class, decode) - : CodeBlock.of("builder.$L.add($L)", fieldName, decode) - : CodeBlock.of("$L.add($L)", fieldName, decode); + assignment = + useBuilder + ? field.getType().equals(ProtoType.STRUCT_NULL) + ? CodeBlock.of("builder.$L.add(($T) $L)", fieldName, Void.class, decode) + : CodeBlock.of("builder.$L.add($L)", fieldName, decode) + : CodeBlock.of("$L.add($L)", fieldName, decode); } else if (field.getType().isMap()) { - return useBuilder - ? CodeBlock.of("builder.$L.putAll($L)", fieldName, decode) - : CodeBlock.of("$L.putAll($L)", fieldName, decode); - } else if (schema.getType(field.getType()) instanceof MessageType && !field.isOneOf()) { + assignment = + useBuilder + ? CodeBlock.of("builder.$L.putAll($L)", fieldName, decode) + : CodeBlock.of("$L.putAll($L)", fieldName, decode); + } else if (schema.getType(field.getType()) instanceof MessageType) { CodeBlock adapter = singleAdapterFor(field, nameAllocator); - return useBuilder - ? CodeBlock.of( - "builder.$L($T.decodeMessageOrMerge($L, reader, builder.$L))", - fieldName, - Internal.class, - adapter, - fieldName) - : CodeBlock.of( - "$L = $T.decodeMessageOrMerge($L, reader, $L)", - fieldName, - Internal.class, - adapter, - fieldName); + assignment = + useBuilder + ? CodeBlock.of( + "builder.$L($T.decodeMessageOrMerge($L, reader, builder.$L))", + fieldName, + Internal.class, + adapter, + fieldName) + : CodeBlock.of( + "$L = $T.decodeMessageOrMerge($L, reader, $L)", + fieldName, + Internal.class, + adapter, + fieldName); } else { - return useBuilder - ? field.getType().equals(ProtoType.STRUCT_NULL) - ? CodeBlock.of("builder.$L(($T) $L)", fieldName, Void.class, decode) - : CodeBlock.of("builder.$L($L)", fieldName, decode) - : CodeBlock.of("$L = $L", fieldName, decode); + assignment = + useBuilder + ? field.getType().equals(ProtoType.STRUCT_NULL) + ? CodeBlock.of("builder.$L(($T) $L)", fieldName, Void.class, decode) + : CodeBlock.of("builder.$L($L)", fieldName, decode) + : CodeBlock.of("$L = $L", fieldName, decode); } + + if (useBuilder || !field.isOneOf()) return assignment; + + OneOf oneOf = null; + for (OneOf candidate : message.getOneOfs()) { + if (candidate.getFields().contains(field)) { + oneOf = candidate; + break; + } + } + if (oneOf == null) return assignment; + + CodeBlock.Builder result = CodeBlock.builder(); + if (schema.getType(field.getType()) instanceof MessageType) { + boolean first = true; + for (Field other : oneOf.getFields()) { + if (other == field) continue; + result.add(first ? "if (" : " || "); + result.add("$N != null", nameAllocator.get(other)); + first = false; + } + if (!first) result.add(") $N = null;\n", fieldName); + } + result.add("$L;\n", assignment); + for (Field other : oneOf.getFields()) { + if (other != field) result.add("$N = null;\n", nameAllocator.get(other)); + } + return result.build(); } private MethodSpec messageAdapterRedact( diff --git a/wire-java-generator/src/test/java/com/squareup/wire/java/JavaGeneratorTest.java b/wire-java-generator/src/test/java/com/squareup/wire/java/JavaGeneratorTest.java index d90ecdf993..e296dadb54 100644 --- a/wire-java-generator/src/test/java/com/squareup/wire/java/JavaGeneratorTest.java +++ b/wire-java-generator/src/test/java/com/squareup/wire/java/JavaGeneratorTest.java @@ -927,6 +927,10 @@ public void usesFieldMask() throws Exception { + " optional google.protobuf.FieldMask mask = 1;\n" + " repeated google.protobuf.FieldMask masks = 2;\n" + " map masks_by_id = 3;\n" + + " oneof choice {\n" + + " google.protobuf.FieldMask oneof_mask = 4;\n" + + " string name = 5;\n" + + " }\n" + "}\n") .build(); String code = new JavaWithProfilesGenerator(schema).generateJava("common.proto.Message"); @@ -938,6 +942,9 @@ public void usesFieldMask() throws Exception { assertThat(code).contains("ProtoAdapter.FIELD_MASK.asRepeated()"); assertThat(code) .contains("ProtoAdapter.newMapAdapter(ProtoAdapter.INT32, ProtoAdapter.FIELD_MASK)"); + assertThat(code) + .contains( + "builder.oneof_mask(Internal.decodeMessageOrMerge(ProtoAdapter.FIELD_MASK, reader, builder.oneof_mask))"); } @Test diff --git a/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt b/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt index 5eea162892..8b2cedc7f8 100644 --- a/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt +++ b/wire-kotlin-generator/src/main/java/com/squareup/wire/kotlin/KotlinGenerator.kt @@ -2062,11 +2062,12 @@ class KotlinGenerator private constructor( fields.forEach { field -> val fieldName = nameAllocator[field] val adapterName = field.getAdapterName() + val flatOneOf = message.flatOneOfs().firstOrNull { field in it.fields } when { field.type!!.isEnum -> { beginControlFlow("%L -> try", field.tag) - addStatement("%L", decodeAndAssign(protoReaderType, field, fieldName, adapterName)) + add("%L\n", decodeAndAssign(protoReaderType, field, fieldName, adapterName, flatOneOf, nameAllocator)) nextControlFlow("catch (e: %T)", ProtoAdapter.EnumConstantNotFoundException::class) addStatement( "reader.addUnknownField(%L, %T.VARINT, e.value.toLong())", @@ -2077,14 +2078,14 @@ class KotlinGenerator private constructor( } field.isPacked && field.isScalar -> { beginControlFlow("%L ->", field.tag) - add(decodeAndAssign(protoReaderType, field, fieldName, adapterName)) + add(decodeAndAssign(protoReaderType, field, fieldName, adapterName, flatOneOf, nameAllocator)) endControlFlow() } else -> { - addStatement( - "%L -> %L", + add( + "%L -> %L\n", field.tag, - decodeAndAssign(protoReaderType, field, fieldName, adapterName), + decodeAndAssign(protoReaderType, field, fieldName, adapterName, flatOneOf, nameAllocator), ) } } @@ -2167,6 +2168,8 @@ class KotlinGenerator private constructor( field: Field, fieldName: String, adapterName: CodeBlock, + oneOf: OneOf?, + nameAllocator: NameAllocator, ): CodeBlock { val decode = if (field.useArray) { CodeBlock.of( @@ -2181,7 +2184,7 @@ class KotlinGenerator private constructor( ) } - return when { + val assignment = when { field.useArray -> { buildCodeBlock { beginControlFlow("if (%N == null)", fieldName) @@ -2217,7 +2220,7 @@ class KotlinGenerator private constructor( field.isRepeated -> CodeBlock.of("%N.add(%L)", fieldName, decode) field.isMap -> CodeBlock.of("%N.putAll(%L)", fieldName, decode) - field.type!!.isMessage && !field.isOneOf -> { + field.type!!.isMessage -> { val decodeMessageOrMerge = MemberName("com.squareup.wire.internal", "decodeMessageOrMerge") if (buildersOnly) { CodeBlock.of("builder.%N(%M(%L, reader, builder.%N))", fieldName, decodeMessageOrMerge, adapterName, fieldName) @@ -2227,6 +2230,26 @@ class KotlinGenerator private constructor( } else -> CodeBlock.of(if (buildersOnly) "builder.%N(%L)" else "%N·= %L", fieldName, decode) } + + if (buildersOnly || oneOf == null) return assignment + + val otherFields = oneOf.fields.filter { it != field } + return buildCodeBlock { + beginControlFlow("run") + if (field.type!!.isMessage && otherFields.isNotEmpty()) { + beginControlFlow( + "if (%L)", + otherFields.joinToCode(separator = " || ") { CodeBlock.of("%N != null", nameAllocator[it]) }, + ) + addStatement("%N = null", fieldName) + endControlFlow() + } + addStatement("%L", assignment) + for (other in otherFields) { + addStatement("%N = null", nameAllocator[other]) + } + endControlFlow() + } } private fun Field.getMinimumByteSize(): Int { diff --git a/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt b/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt index abffb07f8d..512ecfdee2 100644 --- a/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt +++ b/wire-kotlin-generator/src/test/java/com/squareup/wire/kotlin/KotlinGeneratorTest.kt @@ -1516,6 +1516,10 @@ class KotlinGeneratorTest { | optional google.protobuf.FieldMask mask = 1; | repeated google.protobuf.FieldMask masks = 2; | map masks_by_id = 3; + | oneof choice { + | google.protobuf.FieldMask oneof_mask = 4; + | string name = 5; + | } |} """.trimMargin(), ) @@ -1532,6 +1536,9 @@ class KotlinGeneratorTest { assertThat(code).contains("ProtoAdapter.FIELD_MASK") assertThat(code).contains("ProtoAdapter.FIELD_MASK.asRepeated()") assertThat(code).contains("ProtoAdapter.newMapAdapter(ProtoAdapter.INT32, ProtoAdapter.FIELD_MASK)") + assertThat(code).contains( + "oneof_mask = decodeMessageOrMerge(ProtoAdapter.FIELD_MASK, reader, oneof_mask)", + ) } @Test fun wildCommentsAreEscaped() { diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift index e4c0eedb1f..3241532ef7 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift @@ -133,6 +133,21 @@ public final class ProtoDecoder { // MARK: - Internal Methods + /** Decode merged nested-message bytes while retaining the parent reader's recursion depth. */ + internal func decode( + _ type: T.Type, + from data: Data, + recursionDepthOffset: Int + ) throws -> T { + try decodeWithReader( + from: data, + emptyValue: try T(from: .empty), + recursionDepthOffset: recursionDepthOffset + ) { reader in + try reader.decode(type) + } + } + /** Decode a tagged `ProtoDecodable` field from raw data */ internal func decode(_ type: T.Type, from data: Data, withTag tag: UInt32) throws -> T { try decodeWithReader(from: data, emptyValue: nil) { reader in @@ -257,6 +272,7 @@ public final class ProtoDecoder { private func decodeWithReader( from data: Data, emptyValue: @autoclosure () throws -> T?, + recursionDepthOffset: Int = 0, decoder: (ProtoReader) throws -> T ) throws -> T { var value: T? @@ -271,7 +287,11 @@ public final class ProtoDecoder { storage: baseAddress.bindMemory(to: UInt8.self, capacity: buffer.count), count: buffer.count ) - let reader = ProtoReader(buffer: readBuffer, enumDecodingStrategy: enumDecodingStrategy) + let reader = ProtoReader( + buffer: readBuffer, + enumDecodingStrategy: enumDecodingStrategy, + recursionDepthOffset: recursionDepthOffset + ) value = try decoder(reader) } guard let value else { diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift index c5fac27197..485cba79f4 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift @@ -65,6 +65,7 @@ public final class ProtoReader { private let buffer: ReadBuffer private let enumDecodingStrategy: ProtoDecoder.UnknownEnumValueDecodingStrategy + private let recursionDepthOffset: Int /** A stack of frames where each frame represents a level of message nesting. @@ -93,8 +94,13 @@ public final class ProtoReader { // MARK: - Initialization - init(buffer: ReadBuffer, enumDecodingStrategy: ProtoDecoder.UnknownEnumValueDecodingStrategy = .throwError) { + init( + buffer: ReadBuffer, + enumDecodingStrategy: ProtoDecoder.UnknownEnumValueDecodingStrategy = .throwError, + recursionDepthOffset: Int = 0 + ) { self.enumDecodingStrategy = enumDecodingStrategy + self.recursionDepthOffset = recursionDepthOffset self.buffer = buffer self.state = .lengthDelimited(length: buffer.count) @@ -127,7 +133,7 @@ public final class ProtoReader { return -1 } - if (messageStackIndex + 1) >= ProtoReader.recursionLimit { + if recursionDepthOffset + messageStackIndex + 1 >= ProtoReader.recursionLimit { throw ProtoDecoder.Error.recursionLimitExceeded } @@ -338,21 +344,39 @@ public final class ProtoReader { return try T(from: self) } + /** Append the encoded payload of a singular message occurrence for deferred merging. */ + public func decodeMessage(into data: inout Data?) throws { + let occurrence = try decode(Data.self) + if data == nil { + data = occurrence + } else { + data!.append(occurrence) + } + } + + /** + Decode message occurrences accumulated by `decodeMessage(into:)` while preserving the current + nesting depth for recursion-limit enforcement. + */ + public func decodeMergedMessage(_ type: T.Type, from data: Data) throws -> T { + return try ProtoDecoder(enumDecodingStrategy: enumDecodingStrategy).decode( + type, + from: data, + recursionDepthOffset: recursionDepthOffset + messageStackIndex + 1 + ) + } + /** Decode a message field, merging it with an existing value if one is present. - Per the protobuf specification, when a singular embedded-message field appears multiple - times in the encoded input the occurrences are merged: singular scalar fields take the - value from the last occurrence, singular embedded-message fields are merged recursively, - and repeated fields are concatenated. + Generated code uses `decodeMessage(into:)` and `decodeMergedMessage(_:from:)` to accumulate + all occurrences before decoding. This method remains available for source compatibility with + previously generated code. */ public func decode(_ type: T.Type, mergingInto existing: T?) throws -> T { - guard let existing = existing else { - return try decode(type) - } - var data = try ProtoEncoder().encode(existing) + var data = try existing.map { try ProtoEncoder().encode($0) } ?? Data() data.append(try decode(Data.self)) - return try ProtoDecoder(enumDecodingStrategy: enumDecodingStrategy).decode(type, from: data) + return try decodeMergedMessage(type, from: data) } internal func decode(_ type: T.Type, withTag tag: UInt32) throws -> T { diff --git a/wire-runtime-swift/src/test/proto/field_mask_message.proto b/wire-runtime-swift/src/test/proto/field_mask_message.proto index 4a95604841..618c709dc4 100644 --- a/wire-runtime-swift/src/test/proto/field_mask_message.proto +++ b/wire-runtime-swift/src/test/proto/field_mask_message.proto @@ -22,4 +22,12 @@ message MessageContainingFieldMask { google.protobuf.FieldMask mask = 1; repeated google.protobuf.FieldMask masks = 2; map masks_by_id = 3; + oneof choice { + google.protobuf.FieldMask oneof_mask = 4; + string name = 5; + } +} + +message RecursiveMessage { + RecursiveMessage child = 1; } diff --git a/wire-runtime-swift/src/test/proto/simple2.proto b/wire-runtime-swift/src/test/proto/simple2.proto index 2177d0e46c..0337a176e0 100644 --- a/wire-runtime-swift/src/test/proto/simple2.proto +++ b/wire-runtime-swift/src/test/proto/simple2.proto @@ -66,3 +66,12 @@ message SimpleRequired2 { repeated string repeated_string = 11; map map_int32_string = 12; } + +message RequiredPair { + required string a = 1; + required string b = 2; +} + +message ContainsRequiredPair { + optional RequiredPair pair = 1; +} diff --git a/wire-runtime-swift/src/test/swift/FieldMaskTests.swift b/wire-runtime-swift/src/test/swift/FieldMaskTests.swift index f7898c33c1..330ff71aab 100644 --- a/wire-runtime-swift/src/test/swift/FieldMaskTests.swift +++ b/wire-runtime-swift/src/test/swift/FieldMaskTests.swift @@ -101,4 +101,26 @@ final class FieldMaskTests: XCTestCase { XCTAssertEqual(decoded.mask, FieldMask(paths: ["a", "b"])) } + + func testGeneratedMessageMergesDuplicateFieldMaskInSameOneofMember() throws { + let data = Foundation.Data(hexEncoded: "22030a016122030a0162")! + + let decoded = try ProtoDecoder().decode(MessageContainingFieldMask.self, from: data) + + guard case let .oneof_mask(mask) = decoded.choice else { + return XCTFail("Expected oneof_mask, got \(String(describing: decoded.choice))") + } + XCTAssertEqual(mask, FieldMask(paths: ["a", "b"])) + } + + func testGeneratedMessageClearsOneofMergeWhenMemberChanges() throws { + let data = Foundation.Data(hexEncoded: "22030a01612a017822030a0162")! + + let decoded = try ProtoDecoder().decode(MessageContainingFieldMask.self, from: data) + + guard case let .oneof_mask(mask) = decoded.choice else { + return XCTFail("Expected oneof_mask, got \(String(describing: decoded.choice))") + } + XCTAssertEqual(mask, FieldMask(paths: ["b"])) + } } diff --git a/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift b/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift index 13cd063aad..e25d6d1631 100644 --- a/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift +++ b/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift @@ -47,6 +47,26 @@ final class ProtoDecoderTests: XCTestCase { XCTAssertEqual(decoded.nested?.partially_redacted?.name, "kept") } + func testRequiredFieldsMayBeSplitAcrossDuplicatedMessageOccurrences() throws { + // The first RequiredPair occurrence supplies `a`; the second supplies `b`. + let data = Foundation.Data(hexEncoded: "0a030a01610a03120162")! + + let decoded = try ProtoDecoder().decode(ContainsRequiredPair.self, from: data) + + XCTAssertEqual(decoded.pair?.a, "a") + XCTAssertEqual(decoded.pair?.b, "b") + } + + func testDuplicatedMessagesCannotResetRecursionLimit() throws { + let data = duplicatedNestedMessage(depth: 100) + + XCTAssertThrowsError(try ProtoDecoder().decode(RecursiveMessage.self, from: data)) { error in + guard case ProtoDecoder.Error.recursionLimitExceeded = error else { + return XCTFail("Unexpected error: \(error)") + } + } + } + func testDecodeEmptySizeDelimitedData() throws { let decoder = ProtoDecoder() let object = try decoder.decodeSizeDelimited(SimpleOptional2.self, from: Foundation.Data()) @@ -96,4 +116,24 @@ final class ProtoDecoderTests: XCTestCase { XCTAssertEqual(object1, SimpleOptional2()) XCTAssertEqual(object2, SimpleOptional2()) } + + private func duplicatedNestedMessage(depth: Int) -> Foundation.Data { + guard depth > 0 else { return Foundation.Data() } + + let nested = duplicatedNestedMessage(depth: depth - 1) + var result = Foundation.Data([0x0a, 0x00]) // First child occurrence is empty. + result.append(0x0a) // Second child occurrence contains the next level. + appendVarint(UInt64(nested.count), to: &result) + result.append(nested) + return result + } + + private func appendVarint(_ value: UInt64, to data: inout Foundation.Data) { + var value = value + while value >= 0x80 { + data.append(UInt8(truncatingIfNeeded: value) | 0x80) + value >>= 7 + } + data.append(UInt8(value)) + } } diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/AnyMessage.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/AnyMessage.kt index 932a2df4c8..e872b5d21c 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/AnyMessage.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/AnyMessage.kt @@ -78,12 +78,17 @@ class AnyMessage( ) = AnyMessage(typeUrl, value) companion object { + /** Packs a generated [Message] using its built-in adapter. Use this for regular Wire messages. */ fun pack(message: Message<*, *>): AnyMessage { val typeUrl = message.adapter.typeUrl ?: error("recompile ${message::class} to use it with AnyMessage") return AnyMessage(typeUrl, message.encodeByteString()) } + /** + * Packs [value] using an explicit [adapter]. Use this for types that aren't [Message] subtypes, + * such as well-known types like `FieldMask` with [ProtoAdapter.FIELD_MASK]. + */ fun pack(adapter: ProtoAdapter, value: T): AnyMessage { val typeUrl = adapter.typeUrl ?: error("cannot pack ${adapter.type ?: "value"}: the adapter has no type URL") diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt index e3e42c6c93..1ee183eb28 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/FieldMask.kt @@ -21,8 +21,41 @@ package com.squareup.wire * Field masks are used to specify a subset of fields on a target message. Each path uses proto * field names, separated by dots for nested fields. */ -class FieldMask(paths: List = emptyList()) { - val paths: List = paths.toList() +class FieldMask private constructor( + private val pathChunks: PathChunks?, +) { + constructor() : this(emptyList()) + + constructor(paths: List = emptyList()) : this( + paths.takeIf { it.isNotEmpty() }?.let { PathChunks(previous = null, paths = it.toList()) }, + ) + + /** + * The paths in this mask. This is flattened lazily so merging encoded occurrences can append + * chunks in constant time without repeatedly copying all paths accumulated so far. + */ + val paths: List by lazy { + if (pathChunks == null) { + emptyList() + } else { + val chunks = mutableListOf>() + var chunk: PathChunks? = pathChunks + while (chunk != null) { + chunks += chunk.paths + chunk = chunk.previous + } + buildList(pathChunks.size) { + for (i in chunks.size - 1 downTo 0) { + addAll(chunks[i]) + } + } + } + } + + internal fun append(paths: List): FieldMask { + if (paths.isEmpty()) return this + return FieldMask(PathChunks(pathChunks, paths, (pathChunks?.size ?: 0) + paths.size)) + } fun copy(paths: List = this.paths): FieldMask = FieldMask(paths) @@ -35,3 +68,9 @@ class FieldMask(paths: List = emptyList()) { override fun toString(): String = "FieldMask{paths=$paths}" } + +private class PathChunks( + val previous: PathChunks?, + val paths: List, + val size: Int = paths.size, +) diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/Internal.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/Internal.kt index 52e1f04fe1..41bb6c5274 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/Internal.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/Internal.kt @@ -21,6 +21,7 @@ package com.squareup.wire.internal import com.squareup.wire.Duration import com.squareup.wire.FieldEncoding +import com.squareup.wire.FieldMask import com.squareup.wire.Instant import com.squareup.wire.ProtoAdapter import com.squareup.wire.ProtoReader @@ -333,6 +334,10 @@ fun encodeArray_double(array: DoubleArray, writer: ReverseProtoWriter, tag: Int) */ fun decodeMessageOrMerge(adapter: ProtoAdapter, reader: ProtoReader, existing: E?): E { if (existing == null) return adapter.decode(reader) + if (adapter === ProtoAdapter.FIELD_MASK) { + @Suppress("UNCHECKED_CAST") + return (existing as FieldMask).append(ProtoAdapter.FIELD_MASK.decode(reader).paths) as E + } val bytes = reader.readBytes() val buffer = Buffer() adapter.encode(buffer, existing) @@ -342,6 +347,10 @@ fun decodeMessageOrMerge(adapter: ProtoAdapter, reader: ProtoReader, exis fun decodeMessageOrMerge(adapter: ProtoAdapter, reader: ProtoReader32, existing: E?): E { if (existing == null) return adapter.decode(reader) + if (adapter === ProtoAdapter.FIELD_MASK) { + @Suppress("UNCHECKED_CAST") + return (existing as FieldMask).append(ProtoAdapter.FIELD_MASK.decode(reader).paths) as E + } val bytes = reader.readBytes() val buffer = Buffer() adapter.encode(buffer, existing) diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/RuntimeMessageAdapter.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/RuntimeMessageAdapter.kt index 9a518b3241..4bc54e09de 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/RuntimeMessageAdapter.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/internal/RuntimeMessageAdapter.kt @@ -175,8 +175,7 @@ class RuntimeMessageAdapter( } else { val singleAdapter = field.singleAdapter if ((field.isMessage || singleAdapter in MESSAGE_BACKED_BUILT_IN_ADAPTERS) && - !field.label.isRepeated && - !field.label.isOneOf + !field.label.isRepeated ) { @Suppress("UNCHECKED_CAST") val adapter = singleAdapter as ProtoAdapter diff --git a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaProtoAdapterFactory.kt b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaProtoAdapterFactory.kt index 03816193c7..1b133d7ac8 100644 --- a/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaProtoAdapterFactory.kt +++ b/wire-schema/src/commonMain/kotlin/com/squareup/wire/schema/SchemaProtoAdapterFactory.kt @@ -269,6 +269,7 @@ internal class SchemaProtoAdapterFactory( } override fun set(builder: MutableMap, value: Any?) { + oneOf?.fields?.forEach { builder.remove(it.name) } builder[field.name] = value!! } diff --git a/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/DynamicSerializationTest.kt b/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/DynamicSerializationTest.kt index 1e5bdcb918..48c5257b79 100644 --- a/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/DynamicSerializationTest.kt +++ b/wire-schema/src/jvmTest/kotlin/com/squareup/wire/schema/DynamicSerializationTest.kt @@ -112,6 +112,60 @@ class DynamicSerializationTest { .isEqualTo(mapOf("field_mask_field" to FieldMask(listOf("a", "b")))) } + @Test + fun singularFieldMaskOccurrencesInSameOneOfMemberAreMerged() { + val schema = buildSchema { + add( + "message.proto".toPath(), + """ + |syntax = "proto3"; + |import "google/protobuf/field_mask.proto"; + | + |message Message { + | oneof choice { + | google.protobuf.FieldMask field_mask_field = 1; + | string name = 2; + | } + |} + | + """.trimMargin(), + ) + } + + val adapter = schema.protoAdapter(typeName = "Message", includeUnknown = true) + + assertThat(adapter.decode("0a030a01610a030a0162".decodeHex())) + .isEqualTo(mapOf("field_mask_field" to FieldMask(listOf("a", "b")))) + assertThat(adapter.decode("0a030a01611201780a030a0162".decodeHex())) + .isEqualTo(mapOf("field_mask_field" to FieldMask(listOf("b")))) + } + + @Test + fun manySingularFieldMaskOccurrencesAreMerged() { + val schema = buildSchema { + add( + "message.proto".toPath(), + """ + |syntax = "proto3"; + |import "google/protobuf/field_mask.proto"; + | + |message Message { + | google.protobuf.FieldMask field_mask_field = 1; + |} + | + """.trimMargin(), + ) + } + + val adapter = schema.protoAdapter(typeName = "Message", includeUnknown = true) + val encoded = buildList { + repeat(10_000) { add("0a030a0178") } + }.joinToString(separator = "").decodeHex() + + val decoded = adapter.decode(encoded) as Map<*, *> + assertThat((decoded["field_mask_field"] as FieldMask).paths.size).isEqualTo(10_000) + } + @Test fun singularDurationOccurrencesAreMerged() { val schema = buildSchema { 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 b1a7770546..45a6cb6cdf 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 @@ -585,6 +585,28 @@ class SwiftGenerator private constructor( .addParameter("from", reader, protoReader) .throws(true) .apply { + val localNames = propertyNames.toMutableSet().apply { + add(reader) + add(token) + add(tag) + } + fun allocateLocalName(suggested: String): String { + var result = suggested + while (!localNames.add(result)) result = "_$result" + return result + } + + val messageDataNames = type.declaredFields + .filter { it.isMessage && !it.isRepeated && !it.isMap } + .associateWith { allocateLocalName("${it.safeName}ProtoData") } + val oneOfMessageDataNames = type.oneOfs + .flatMap { it.fields } + .filter { it.isMessage } + .associateWith { allocateLocalName("${it.safeName}ProtoData") } + val oneOfTagNames = type.oneOfs + .filter { oneOf -> oneOf.fields.any { it.isMessage } } + .associateWith { allocateLocalName("${it.name}ProtoTag") } + // Declare locals into which everything is written before promoting to members. type.declaredFields.forEach { field -> val localType = when (type.syntax) { @@ -611,9 +633,20 @@ class SwiftGenerator private constructor( addStatement("var %N: %T = %L", field.safeName, localType, initializer) } + messageDataNames.values.forEach { dataName -> + addStatement("var %N: %T = nil", dataName, FOUNDATION_DATA.makeOptional()) + } type.oneOfs.forEach { oneOf -> val enumName = oneOfSafeDeclaredTypeName(oneOf, type, oneOfEnumNames) addStatement("var %N: %T = nil", oneOf.name, enumName.makeOptional()) + oneOfTagNames[oneOf]?.let { tagName -> + addStatement("var %N: %T = nil", tagName, UINT32.makeOptional()) + } + oneOf.fields.forEach { field -> + oneOfMessageDataNames[field]?.let { dataName -> + addStatement("var %N: %T = nil", dataName, FOUNDATION_DATA.makeOptional()) + } + } } if (type.declaredFieldsAndOneOfFields.isNotEmpty()) { addStatement("") @@ -636,11 +669,7 @@ class SwiftGenerator private constructor( if (field.isRepeated) { decoder.add("try $reader.decode(into: &%N", field.safeName) } else if (field.isMessage) { - // Duplicated occurrences of a singular message field are merged per the protobuf - // specification, matching generated Kotlin and Java. - val typeName = field.typeName.makeNonOptional() - - decoder.add("%N = try $reader.decode(%T.self, mergingInto: %N", field.safeName, typeName, field.safeName) + decoder.add("try $reader.decodeMessage(into: &%N", messageDataNames.getValue(field)) } else { val typeName = field.typeName.makeNonOptional() @@ -656,24 +685,58 @@ class SwiftGenerator private constructor( type.oneOfs.forEach { oneOf -> oneOf.fields.forEach { field -> when { - // ProtoReader.decode() return optional for enums. Handle that specially. - field.isEnum -> { + field.isMessage -> { + val tagName = oneOfTagNames.getValue(oneOf) + val dataName = oneOfMessageDataNames.getValue(field) addStatement( - "case %1L: %2N = (try $reader.decode(%4T.self)).flatMap { .%3N(\$0) }", + "case %1L: if %2N != %1L { %3N = nil }; %2N = %1L; try $reader.decodeMessage(into: &%3N)", field.tag, - oneOf.name, - field.safeName, - field.typeName.makeNonOptional(), + tagName, + dataName, ) } + // ProtoReader.decode() return optional for enums. Handle that specially. + field.isEnum -> { + val tagName = oneOfTagNames[oneOf] + if (tagName != null) { + addStatement( + "case %1L: %5N = %1L; %2N = (try $reader.decode(%4T.self)).flatMap { .%3N(\$0) }", + field.tag, + oneOf.name, + field.safeName, + field.typeName.makeNonOptional(), + tagName, + ) + } else { + addStatement( + "case %1L: %2N = (try $reader.decode(%4T.self)).flatMap { .%3N(\$0) }", + field.tag, + oneOf.name, + field.safeName, + field.typeName.makeNonOptional(), + ) + } + } else -> { - addStatement( - "case %1L: %2N = .%3N(try $reader.decode(%4T.self))", - field.tag, - oneOf.name, - field.safeName, - field.typeName.makeNonOptional(), - ) + val tagName = oneOfTagNames[oneOf] + if (tagName != null) { + addStatement( + "case %1L: %5N = %1L; %2N = .%3N(try $reader.decode(%4T.self))", + field.tag, + oneOf.name, + field.safeName, + field.typeName.makeNonOptional(), + tagName, + ) + } else { + addStatement( + "case %1L: %2N = .%3N(try $reader.decode(%4T.self))", + field.tag, + oneOf.name, + field.safeName, + field.typeName.makeNonOptional(), + ) + } } } } @@ -681,6 +744,32 @@ class SwiftGenerator private constructor( addStatement("default: try $reader.readUnknownField(tag: $tag)") endControlFlow("switch") endControlFlow("while") + + messageDataNames.forEach { (field, dataName) -> + beginControlFlow("if", "let %N", dataName) + addStatement( + "%N = try $reader.decodeMergedMessage(%T.self, from: %N)", + field.safeName, + field.typeName.makeNonOptional(), + dataName, + ) + endControlFlow("if") + } + type.oneOfs.forEach { oneOf -> + val tagName = oneOfTagNames[oneOf] ?: return@forEach + oneOf.fields.forEach { field -> + val dataName = oneOfMessageDataNames[field] ?: return@forEach + beginControlFlow("if", "%N == %L, let %N", tagName, field.tag, dataName) + addStatement( + "%N = .%N(try $reader.decodeMergedMessage(%T.self, from: %N))", + oneOf.name, + field.safeName, + field.typeName.makeNonOptional(), + dataName, + ) + endControlFlow("if") + } + } addStatement("self.unknownFields = try $reader.endMessage(token: $token)") // Check required and bind members. 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 6ec03f163d..ffbda72d89 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 @@ -95,10 +95,14 @@ class SwiftGeneratorTest { assertThat(code).contains("public var masks: [FieldMask]") assertThat(code).contains("public var masks_by_id: [Int32 : FieldMask]") assertThat(code).contains("case oneof_mask(FieldMask)") - assertThat(code).contains("mask = try protoReader.decode(FieldMask.self, mergingInto: mask)") + assertThat(code).contains("case 1: try protoReader.decodeMessage(into: &maskProtoData)") + assertThat(code).contains("mask = try protoReader.decodeMergedMessage(FieldMask.self, from: maskProtoData)") assertThat(code).contains("try protoReader.decode(into: &masks)") assertThat(code).contains("try protoReader.decode(into: &masks_by_id, keyEncoding: .variable)") - assertThat(code).contains("case 4: choice = .oneof_mask(try protoReader.decode(FieldMask.self))") + assertThat(code).contains("case 4: if choiceProtoTag != 4 { oneof_maskProtoData = nil }") + assertThat(code).contains( + "choice = .oneof_mask(try protoReader.decodeMergedMessage(FieldMask.self, from: oneof_maskProtoData))", + ) assertThat(code).doesNotContain("@ProtoDefaulted") } @@ -128,10 +132,13 @@ class SwiftGeneratorTest { val code = schema.generateSwift("squareup.protos3.Message") - assertThat(code).contains("other = try protoReader.decode(Other.self, mergingInto: other)") - // Repeated and oneof message fields don't merge. + assertThat(code).contains("case 1: try protoReader.decodeMessage(into: &otherProtoData)") + assertThat(code).contains("other = try protoReader.decodeMergedMessage(Other.self, from: otherProtoData)") assertThat(code).contains("try protoReader.decode(into: &others)") - assertThat(code).contains("case 3: choice = .oneof_other(try protoReader.decode(Other.self))") + assertThat(code).contains("case 3: if choiceProtoTag != 3 { oneof_otherProtoData = nil }") + assertThat(code).contains( + "choice = .oneof_other(try protoReader.decodeMergedMessage(Other.self, from: oneof_otherProtoData))", + ) } private fun Schema.generateSwift(typeName: String): String { diff --git a/wire-tests-proto3-swift/src/main/swift/ContainsDuration.swift b/wire-tests-proto3-swift/src/main/swift/ContainsDuration.swift index 7719fdc22f..25cadcef1f 100644 --- a/wire-tests-proto3-swift/src/main/swift/ContainsDuration.swift +++ b/wire-tests-proto3-swift/src/main/swift/ContainsDuration.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos3.kotlin.contains_duration.ContainsDuration in contains_duration.proto +import Foundation import Wire public struct ContainsDuration { @@ -45,14 +46,18 @@ extension ContainsDuration : Proto3Codable { public init(from protoReader: ProtoReader) throws { var duration: Duration? = nil + var durationProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { - case 1: duration = try protoReader.decode(Duration.self, mergingInto: duration) + case 1: try protoReader.decodeMessage(into: &durationProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let durationProtoData { + duration = try protoReader.decodeMergedMessage(Duration.self, from: durationProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self.duration = duration diff --git a/wire-tests-proto3-swift/src/main/swift/ContainsTimestamp.swift b/wire-tests-proto3-swift/src/main/swift/ContainsTimestamp.swift index 696ae3b295..cf795d801b 100644 --- a/wire-tests-proto3-swift/src/main/swift/ContainsTimestamp.swift +++ b/wire-tests-proto3-swift/src/main/swift/ContainsTimestamp.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos3.kotlin.contains_timestamp.ContainsTimestamp in contains_timestamp.proto +import Foundation import Wire public struct ContainsTimestamp { @@ -45,14 +46,18 @@ extension ContainsTimestamp : Proto3Codable { public init(from protoReader: ProtoReader) throws { var timestamp: Timestamp? = nil + var timestampProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { - case 1: timestamp = try protoReader.decode(Timestamp.self, mergingInto: timestamp) + case 1: try protoReader.decodeMessage(into: ×tampProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let timestampProtoData { + timestamp = try protoReader.decodeMergedMessage(Timestamp.self, from: timestampProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self.timestamp = timestamp diff --git a/wire-tests-swift/manifest/module_one/SwiftModuleOneAmbiguousMessage.swift b/wire-tests-swift/manifest/module_one/SwiftModuleOneAmbiguousMessage.swift index 2f80631726..f2d47b67e6 100644 --- a/wire-tests-swift/manifest/module_one/SwiftModuleOneAmbiguousMessage.swift +++ b/wire-tests-swift/manifest/module_one/SwiftModuleOneAmbiguousMessage.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.swift_modules.module_one.SwiftModuleOneAmbiguousMessage in swift_module_one.proto +import Foundation import Wire import module_address import module_location @@ -52,16 +53,24 @@ extension SwiftModuleOneAmbiguousMessage : Proto2Codable { var name: String? = nil var address: module_address.Address? = nil var location: Location? = nil + var addressProtoData: Foundation.Data? = nil + var locationProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: name = try protoReader.decode(String.self) - case 2: address = try protoReader.decode(module_address.Address.self, mergingInto: address) - case 3: location = try protoReader.decode(Location.self, mergingInto: location) + case 2: try protoReader.decodeMessage(into: &addressProtoData) + case 3: try protoReader.decodeMessage(into: &locationProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let addressProtoData { + address = try protoReader.decodeMergedMessage(module_address.Address.self, from: addressProtoData) + } + if let locationProtoData { + location = try protoReader.decodeMergedMessage(Location.self, from: locationProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._name.wrappedValue = name diff --git a/wire-tests-swift/manifest/module_three/DateTime.swift b/wire-tests-swift/manifest/module_three/DateTime.swift index 08d2a5e866..9ba3a49f95 100644 --- a/wire-tests-swift/manifest/module_three/DateTime.swift +++ b/wire-tests-swift/manifest/module_three/DateTime.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.swift_modules.module_three.DateTime in swift_module_three.proto +import Foundation import Wire import module_one @@ -46,14 +47,18 @@ extension DateTime : Proto2Codable { public init(from protoReader: ProtoReader) throws { var value: module_one.DateTime? = nil + var valueProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { - case 1: value = try protoReader.decode(module_one.DateTime.self, mergingInto: value) + case 1: try protoReader.decodeMessage(into: &valueProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let valueProtoData { + value = try protoReader.decodeMergedMessage(module_one.DateTime.self, from: valueProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self.value = value diff --git a/wire-tests-swift/manifest/module_two/SwiftModuleTwoMessage.swift b/wire-tests-swift/manifest/module_two/SwiftModuleTwoMessage.swift index 3bd93668a7..f93d4a0396 100644 --- a/wire-tests-swift/manifest/module_two/SwiftModuleTwoMessage.swift +++ b/wire-tests-swift/manifest/module_two/SwiftModuleTwoMessage.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.swift_modules.module_two.SwiftModuleTwoMessage in swift_module_two.proto +import Foundation import Wire import module_one @@ -143,15 +144,19 @@ extension SwiftModuleTwoMessage.NestedMessage : Proto2Codable { public init(from protoReader: ProtoReader) throws { var array_types: [SwiftModuleOneEnum] = [] var module_type: SwiftModuleOneMessage? = nil + var module_typeProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: try protoReader.decode(into: &array_types) - case 2: module_type = try protoReader.decode(SwiftModuleOneMessage.self, mergingInto: module_type) + case 2: try protoReader.decodeMessage(into: &module_typeProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let module_typeProtoData { + module_type = try protoReader.decodeMergedMessage(SwiftModuleOneMessage.self, from: module_typeProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self.array_types = array_types 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 d9a793a773..f9fb25df59 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/AllTypes.swift @@ -2373,6 +2373,9 @@ extension AllTypes.Storage : Proto2Codable { var array_sfixed64: [Int64] = [] var array_float: [Float] = [] var array_double: [Double] = [] + var opt_nested_messageProtoData: Foundation.Data? = nil + var opt_field_maskProtoData: Foundation.Data? = nil + var req_nested_messageProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { @@ -2393,8 +2396,8 @@ extension AllTypes.Storage : Proto2Codable { case 14: opt_string = try protoReader.decode(String.self) case 15: opt_bytes = try protoReader.decode(Foundation.Data.self) case 16: opt_nested_enum = try protoReader.decode(AllTypes.NestedEnum.self) - case 17: opt_nested_message = try protoReader.decode(AllTypes.NestedMessage.self, mergingInto: opt_nested_message) - case 18: opt_field_mask = try protoReader.decode(FieldMask.self, mergingInto: opt_field_mask) + case 17: try protoReader.decodeMessage(into: &opt_nested_messageProtoData) + case 18: try protoReader.decodeMessage(into: &opt_field_maskProtoData) case 101: req_int32 = try protoReader.decode(Int32.self, encoding: .variable) case 102: req_uint32 = try protoReader.decode(UInt32.self, encoding: .variable) case 103: req_sint32 = try protoReader.decode(Int32.self, encoding: .signed) @@ -2411,7 +2414,7 @@ extension AllTypes.Storage : Proto2Codable { case 114: req_string = try protoReader.decode(String.self) case 115: req_bytes = try protoReader.decode(Foundation.Data.self) case 116: req_nested_enum = try protoReader.decode(AllTypes.NestedEnum.self) - case 117: req_nested_message = try protoReader.decode(AllTypes.NestedMessage.self, mergingInto: req_nested_message) + case 117: try protoReader.decodeMessage(into: &req_nested_messageProtoData) case 201: try protoReader.decode(into: &rep_int32, encoding: .variable) case 202: try protoReader.decode(into: &rep_uint32, encoding: .variable) case 203: try protoReader.decode(into: &rep_sint32, encoding: .signed) @@ -2480,6 +2483,15 @@ extension AllTypes.Storage : Proto2Codable { default: try protoReader.readUnknownField(tag: tag) } } + if let opt_nested_messageProtoData { + opt_nested_message = try protoReader.decodeMergedMessage(AllTypes.NestedMessage.self, from: opt_nested_messageProtoData) + } + if let opt_field_maskProtoData { + opt_field_mask = try protoReader.decodeMergedMessage(FieldMask.self, from: opt_field_maskProtoData) + } + if let req_nested_messageProtoData { + req_nested_message = try protoReader.decodeMergedMessage(AllTypes.NestedMessage.self, from: req_nested_messageProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._opt_int32.wrappedValue = opt_int32 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 4ac0ca2880..61b1ee177e 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/FooBar.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.custom_options.FooBar in custom_options.proto +import Foundation import Wire public struct FooBar { @@ -124,13 +125,14 @@ extension FooBar : Proto2Codable { var fred: [Float] = [] var daisy: Double? = nil var nested: [FooBar] = [] + var bazProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: foo = try protoReader.decode(Int32.self, encoding: .variable) case 2: bar = try protoReader.decode(String.self) - case 3: baz = try protoReader.decode(FooBar.Nested.self, mergingInto: baz) + case 3: try protoReader.decodeMessage(into: &bazProtoData) case 4: qux = try protoReader.decode(UInt64.self, encoding: .variable) case 5: try protoReader.decode(into: &fred) case 6: daisy = try protoReader.decode(Double.self) @@ -138,6 +140,9 @@ extension FooBar : Proto2Codable { default: try protoReader.readUnknownField(tag: tag) } } + if let bazProtoData { + baz = try protoReader.decodeMergedMessage(FooBar.Nested.self, from: bazProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._foo.wrappedValue = foo diff --git a/wire-tests-swift/no-manifest/src/main/swift/Form.swift b/wire-tests-swift/no-manifest/src/main/swift/Form.swift index 158063fa83..ca116d211e 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/Form.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/Form.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.oneof.Form in form.proto +import Foundation import Wire public struct Form { @@ -46,23 +47,36 @@ extension Form : Proto2Codable { public init(from protoReader: ProtoReader) throws { var choice: Choice? = nil + var choiceProtoTag: UInt32? = nil + var button_elementProtoData: Foundation.Data? = nil + var local_image_elementProtoData: Foundation.Data? = nil + var remote_image_elementProtoData: Foundation.Data? = nil + var money_elementProtoData: Foundation.Data? = nil + var spacer_elementProtoData: Foundation.Data? = nil + var text_elementProtoData: Foundation.Data? = nil + var customized_card_elementProtoData: Foundation.Data? = nil + var address_elementProtoData: Foundation.Data? = nil + var text_input_elementProtoData: Foundation.Data? = nil + var option_picker_elementProtoData: Foundation.Data? = nil + var detail_row_elementProtoData: Foundation.Data? = nil + var currency_conversion_flags_elementProtoData: Foundation.Data? = nil var decision: Decision? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { - case 1: choice = .button_element(try protoReader.decode(Form.ButtonElement.self)) - case 2: choice = .local_image_element(try protoReader.decode(Form.LocalImageElement.self)) - case 3: choice = .remote_image_element(try protoReader.decode(Form.RemoteImageElement.self)) - case 4: choice = .money_element(try protoReader.decode(Form.MoneyElement.self)) - case 5: choice = .spacer_element(try protoReader.decode(Form.SpacerElement.self)) - case 6: choice = .text_element(try protoReader.decode(Form.TextElement.self)) - case 7: choice = .customized_card_element(try protoReader.decode(Form.CustomizedCardElement.self)) - case 8: choice = .address_element(try protoReader.decode(Form.AddressElement.self)) - case 9: choice = .text_input_element(try protoReader.decode(Form.TextInputElement.self)) - case 10: choice = .option_picker_element(try protoReader.decode(Form.OptionPickerElement.self)) - case 11: choice = .detail_row_element(try protoReader.decode(Form.DetailRowElement.self)) - case 12: choice = .currency_conversion_flags_element(try protoReader.decode(Form.CurrencyConversionFlagsElement.self)) + case 1: if choiceProtoTag != 1 { button_elementProtoData = nil }; choiceProtoTag = 1; try protoReader.decodeMessage(into: &button_elementProtoData) + case 2: if choiceProtoTag != 2 { local_image_elementProtoData = nil }; choiceProtoTag = 2; try protoReader.decodeMessage(into: &local_image_elementProtoData) + case 3: if choiceProtoTag != 3 { remote_image_elementProtoData = nil }; choiceProtoTag = 3; try protoReader.decodeMessage(into: &remote_image_elementProtoData) + case 4: if choiceProtoTag != 4 { money_elementProtoData = nil }; choiceProtoTag = 4; try protoReader.decodeMessage(into: &money_elementProtoData) + case 5: if choiceProtoTag != 5 { spacer_elementProtoData = nil }; choiceProtoTag = 5; try protoReader.decodeMessage(into: &spacer_elementProtoData) + case 6: if choiceProtoTag != 6 { text_elementProtoData = nil }; choiceProtoTag = 6; try protoReader.decodeMessage(into: &text_elementProtoData) + case 7: if choiceProtoTag != 7 { customized_card_elementProtoData = nil }; choiceProtoTag = 7; try protoReader.decodeMessage(into: &customized_card_elementProtoData) + case 8: if choiceProtoTag != 8 { address_elementProtoData = nil }; choiceProtoTag = 8; try protoReader.decodeMessage(into: &address_elementProtoData) + case 9: if choiceProtoTag != 9 { text_input_elementProtoData = nil }; choiceProtoTag = 9; try protoReader.decodeMessage(into: &text_input_elementProtoData) + case 10: if choiceProtoTag != 10 { option_picker_elementProtoData = nil }; choiceProtoTag = 10; try protoReader.decodeMessage(into: &option_picker_elementProtoData) + case 11: if choiceProtoTag != 11 { detail_row_elementProtoData = nil }; choiceProtoTag = 11; try protoReader.decodeMessage(into: &detail_row_elementProtoData) + case 12: if choiceProtoTag != 12 { currency_conversion_flags_elementProtoData = nil }; choiceProtoTag = 12; try protoReader.decodeMessage(into: ¤cy_conversion_flags_elementProtoData) case 101: decision = .a(try protoReader.decode(String.self)) case 102: decision = .b(try protoReader.decode(String.self)) case 103: decision = .c(try protoReader.decode(String.self)) @@ -74,6 +88,42 @@ extension Form : Proto2Codable { default: try protoReader.readUnknownField(tag: tag) } } + if choiceProtoTag == 1, let button_elementProtoData { + choice = .button_element(try protoReader.decodeMergedMessage(Form.ButtonElement.self, from: button_elementProtoData)) + } + if choiceProtoTag == 2, let local_image_elementProtoData { + choice = .local_image_element(try protoReader.decodeMergedMessage(Form.LocalImageElement.self, from: local_image_elementProtoData)) + } + if choiceProtoTag == 3, let remote_image_elementProtoData { + choice = .remote_image_element(try protoReader.decodeMergedMessage(Form.RemoteImageElement.self, from: remote_image_elementProtoData)) + } + if choiceProtoTag == 4, let money_elementProtoData { + choice = .money_element(try protoReader.decodeMergedMessage(Form.MoneyElement.self, from: money_elementProtoData)) + } + if choiceProtoTag == 5, let spacer_elementProtoData { + choice = .spacer_element(try protoReader.decodeMergedMessage(Form.SpacerElement.self, from: spacer_elementProtoData)) + } + if choiceProtoTag == 6, let text_elementProtoData { + choice = .text_element(try protoReader.decodeMergedMessage(Form.TextElement.self, from: text_elementProtoData)) + } + if choiceProtoTag == 7, let customized_card_elementProtoData { + choice = .customized_card_element(try protoReader.decodeMergedMessage(Form.CustomizedCardElement.self, from: customized_card_elementProtoData)) + } + if choiceProtoTag == 8, let address_elementProtoData { + choice = .address_element(try protoReader.decodeMergedMessage(Form.AddressElement.self, from: address_elementProtoData)) + } + if choiceProtoTag == 9, let text_input_elementProtoData { + choice = .text_input_element(try protoReader.decodeMergedMessage(Form.TextInputElement.self, from: text_input_elementProtoData)) + } + if choiceProtoTag == 10, let option_picker_elementProtoData { + choice = .option_picker_element(try protoReader.decodeMergedMessage(Form.OptionPickerElement.self, from: option_picker_elementProtoData)) + } + if choiceProtoTag == 11, let detail_row_elementProtoData { + choice = .detail_row_element(try protoReader.decodeMergedMessage(Form.DetailRowElement.self, from: detail_row_elementProtoData)) + } + if choiceProtoTag == 12, let currency_conversion_flags_elementProtoData { + choice = .currency_conversion_flags_element(try protoReader.decodeMergedMessage(Form.CurrencyConversionFlagsElement.self, from: currency_conversion_flags_elementProtoData)) + } self.unknownFields = try protoReader.endMessage(token: token) self.choice = choice diff --git a/wire-tests-swift/no-manifest/src/main/swift/OuterMessage.swift b/wire-tests-swift/no-manifest/src/main/swift/OuterMessage.swift index ac2f37d8a2..6147ea8215 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/OuterMessage.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/OuterMessage.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.packed_encoding.OuterMessage in packed_encoding.proto +import Foundation import Wire public struct OuterMessage { @@ -49,15 +50,19 @@ extension OuterMessage : Proto2Codable { public init(from protoReader: ProtoReader) throws { var outer_number_before: Int32? = nil var embedded_message: EmbeddedMessage? = nil + var embedded_messageProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: outer_number_before = try protoReader.decode(Int32.self, encoding: .variable) - case 2: embedded_message = try protoReader.decode(EmbeddedMessage.self, mergingInto: embedded_message) + case 2: try protoReader.decodeMessage(into: &embedded_messageProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if let embedded_messageProtoData { + embedded_message = try protoReader.decodeMergedMessage(EmbeddedMessage.self, from: embedded_messageProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._outer_number_before.wrappedValue = outer_number_before diff --git a/wire-tests-swift/no-manifest/src/main/swift/Screen.swift b/wire-tests-swift/no-manifest/src/main/swift/Screen.swift index c866097fe5..e0f86ce78a 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/Screen.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/Screen.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.swift_modules.Screen in swift_edge_cases.proto +import Foundation import Wire public struct Screen { @@ -46,20 +47,29 @@ extension Screen : Proto2Codable { public init(from protoReader: ProtoReader) throws { var screen: Screen_OneOf? = nil + var screenProtoTag: UInt32? = nil + var screen_oneof_sub_messageProtoData: Foundation.Data? = nil + var selfProtoData: Foundation.Data? = nil var view: View? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { - case 1: screen = .screen_oneof_string(try protoReader.decode(String.self)) - case 2: screen = .screen_oneof_int32(try protoReader.decode(Int32.self)) - case 3: screen = .screen_oneof_sub_message(try protoReader.decode(Screen.SubMessage.self)) - case 4: screen = .`self`(try protoReader.decode(Screen.Self_.self)) + case 1: screenProtoTag = 1; screen = .screen_oneof_string(try protoReader.decode(String.self)) + case 2: screenProtoTag = 2; screen = .screen_oneof_int32(try protoReader.decode(Int32.self)) + case 3: if screenProtoTag != 3 { screen_oneof_sub_messageProtoData = nil }; screenProtoTag = 3; try protoReader.decodeMessage(into: &screen_oneof_sub_messageProtoData) + case 4: if screenProtoTag != 4 { selfProtoData = nil }; screenProtoTag = 4; try protoReader.decodeMessage(into: &selfProtoData) case 5: view = .view_oneof_string(try protoReader.decode(String.self)) case 6: view = .container(try protoReader.decode(String.self)) default: try protoReader.readUnknownField(tag: tag) } } + if screenProtoTag == 3, let screen_oneof_sub_messageProtoData { + screen = .screen_oneof_sub_message(try protoReader.decodeMergedMessage(Screen.SubMessage.self, from: screen_oneof_sub_messageProtoData)) + } + if screenProtoTag == 4, let selfProtoData { + screen = .`self`(try protoReader.decodeMergedMessage(Screen.Self_.self, from: selfProtoData)) + } self.unknownFields = try protoReader.endMessage(token: token) self.screen = screen @@ -310,17 +320,22 @@ extension Screen.SubMessage : Proto2Codable { var string: String? = nil var screen: Screen.SubMessage.Screen_OneOf? = nil var submessage: Screen.SubMessage.Submessage_OneOf? = nil + var submessageProtoTag: UInt32? = nil + var submessage_oneof_nested_sub_messageProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: string = try protoReader.decode(String.self) case 2: screen = .submessage_screen_oneof_string(try protoReader.decode(String.self)) - case 3: submessage = .submessage_oneof_string(try protoReader.decode(String.self)) - case 4: submessage = .submessage_oneof_nested_sub_message(try protoReader.decode(Screen.SubMessage.NestedSubMessage.self)) + case 3: submessageProtoTag = 3; submessage = .submessage_oneof_string(try protoReader.decode(String.self)) + case 4: if submessageProtoTag != 4 { submessage_oneof_nested_sub_messageProtoData = nil }; submessageProtoTag = 4; try protoReader.decodeMessage(into: &submessage_oneof_nested_sub_messageProtoData) default: try protoReader.readUnknownField(tag: tag) } } + if submessageProtoTag == 4, let submessage_oneof_nested_sub_messageProtoData { + submessage = .submessage_oneof_nested_sub_message(try protoReader.decodeMergedMessage(Screen.SubMessage.NestedSubMessage.self, from: submessage_oneof_nested_sub_messageProtoData)) + } self.unknownFields = try protoReader.endMessage(token: token) self._string.wrappedValue = string diff --git a/wire-tests-swift/no-manifest/src/main/swift/VersionOne.swift b/wire-tests-swift/no-manifest/src/main/swift/VersionOne.swift index 5b2ad94e9d..e86854e648 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/VersionOne.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/VersionOne.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.unknownfields.VersionOne in unknown_fields.proto +import Foundation import Wire public struct VersionOne { @@ -51,16 +52,20 @@ extension VersionOne : Proto2Codable { var i: Int32? = nil var obj: NestedVersionOne? = nil var en: EnumVersionOne? = nil + var objProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { switch tag { case 1: i = try protoReader.decode(Int32.self, encoding: .variable) - case 7: obj = try protoReader.decode(NestedVersionOne.self, mergingInto: obj) + case 7: try protoReader.decodeMessage(into: &objProtoData) case 8: en = try protoReader.decode(EnumVersionOne.self) default: try protoReader.readUnknownField(tag: tag) } } + if let objProtoData { + obj = try protoReader.decodeMergedMessage(NestedVersionOne.self, from: objProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._i.wrappedValue = i diff --git a/wire-tests-swift/no-manifest/src/main/swift/VersionTwo.swift b/wire-tests-swift/no-manifest/src/main/swift/VersionTwo.swift index 0c536de5a7..ae7638434d 100644 --- a/wire-tests-swift/no-manifest/src/main/swift/VersionTwo.swift +++ b/wire-tests-swift/no-manifest/src/main/swift/VersionTwo.swift @@ -1,5 +1,6 @@ // Code generated by Wire protocol buffer compiler, do not edit. // Source: squareup.protos.kotlin.unknownfields.VersionTwo in unknown_fields.proto +import Foundation import Wire public struct VersionTwo { @@ -65,6 +66,7 @@ extension VersionTwo : Proto2Codable { var v2_rs: [String] = [] var obj: NestedVersionTwo? = nil var en: EnumVersionTwo? = nil + var objProtoData: Foundation.Data? = nil let token = try protoReader.beginMessage() while let tag = try protoReader.nextTag(token: token) { @@ -75,11 +77,14 @@ extension VersionTwo : Proto2Codable { case 4: v2_f32 = try protoReader.decode(UInt32.self, encoding: .fixed) case 5: v2_f64 = try protoReader.decode(UInt64.self, encoding: .fixed) case 6: try protoReader.decode(into: &v2_rs) - case 7: obj = try protoReader.decode(NestedVersionTwo.self, mergingInto: obj) + case 7: try protoReader.decodeMessage(into: &objProtoData) case 8: en = try protoReader.decode(EnumVersionTwo.self) default: try protoReader.readUnknownField(tag: tag) } } + if let objProtoData { + obj = try protoReader.decodeMergedMessage(NestedVersionTwo.self, from: objProtoData) + } self.unknownFields = try protoReader.endMessage(token: token) self._i.wrappedValue = i