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 b310f78faf..1755d1f063 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 @@ -60,6 +60,7 @@ import com.squareup.wire.ProtoWriter; import com.squareup.wire.ReverseProtoWriter; import com.squareup.wire.Syntax; +import com.squareup.wire.WireEnclosingType; import com.squareup.wire.WireEnum; import com.squareup.wire.WireEnumConstant; import com.squareup.wire.WireField; @@ -113,6 +114,7 @@ public final class JavaGenerator { static final ClassName STRING = ClassName.get(String.class); static final ClassName LIST = ClassName.get(List.class); static final ClassName MESSAGE = ClassName.get(Message.class); + static final ClassName WIRE_ENCLOSING_TYPE = ClassName.get(WireEnclosingType.class); static final ClassName ANDROID_MESSAGE = MESSAGE.peerClass("AndroidMessage"); static final ClassName ADAPTER = ClassName.get(ProtoAdapter.class); static final ClassName BUILDER = ClassName.get(Message.Builder.class); @@ -884,7 +886,9 @@ private TypeSpec generateEnclosingType(EnclosingType type) { ClassName javaType = (ClassName) typeName(type.getType()); TypeSpec.Builder builder = - TypeSpec.classBuilder(javaType.simpleName()).addModifiers(PUBLIC, FINAL); + TypeSpec.classBuilder(javaType.simpleName()) + .addModifiers(PUBLIC, FINAL) + .addAnnotation(WIRE_ENCLOSING_TYPE); if (javaType.enclosingClassName() != null) { builder.addModifiers(STATIC); } 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 947803380b..10f51b4f91 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 @@ -604,6 +604,7 @@ public void enclosingTypeIsNotMessage() throws IOException { assertThat(javaOutput) .contains( "" + + "@WireEnclosingType\n" + "public final class A {\n" + " private A() {\n" + " throw new AssertionError();\n" 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 0073dcb032..20414ebf68 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 @@ -68,6 +68,7 @@ import com.squareup.wire.ProtoReader32 import com.squareup.wire.ProtoWriter import com.squareup.wire.ReverseProtoWriter import com.squareup.wire.Syntax +import com.squareup.wire.WireEnclosingType import com.squareup.wire.WireEnum import com.squareup.wire.WireEnumConstant import com.squareup.wire.WireField @@ -2547,6 +2548,7 @@ class KotlinGenerator private constructor( private fun generateEnclosing(type: EnclosingType): TypeSpec { val classBuilder = TypeSpec.classBuilder(type.typeName as ClassName) + .addAnnotation(WIRE_ENCLOSING_TYPE) .primaryConstructor(FunSpec.constructorBuilder().addModifiers(PRIVATE).build()) type.nestedTypes.forEach { classBuilder.addType(generateType(it)) } @@ -3037,6 +3039,7 @@ class KotlinGenerator private constructor( ProtoType.UINT32 to CodeBlock.of("0"), ) private val MESSAGE = Message::class.asClassName() + private val WIRE_ENCLOSING_TYPE = WireEnclosingType::class.asClassName() private val ANDROID_MESSAGE = MESSAGE.peerClass("AndroidMessage") private val PROTO_READER = ProtoReader::class.asClassName() private val PROTO_READER_32 = ProtoReader32::class.asClassName() 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 4c7cbd27d1..12873e7a06 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 @@ -202,7 +202,12 @@ class KotlinGeneratorTest { val kotlinGenerator = KotlinGenerator.invoke(pruned) val typeSpec = kotlinGenerator.generateType(pruned.getType("A")!!) val code = FileSpec.get("", typeSpec).toString() - assertThat(code).contains("class A private constructor() {") + assertThat(code).contains( + """ + |@WireEnclosingType + |public class A private constructor() { + """.trimMargin(), + ) assertWithMessage(code).that(code.contains("class B(.*) : Message".toRegex(DOT_MATCHES_ALL))).isTrue() } diff --git a/wire-runtime/api/wire-runtime.api b/wire-runtime/api/wire-runtime.api index 99e042e2aa..baabe5af8c 100644 --- a/wire-runtime/api/wire-runtime.api +++ b/wire-runtime/api/wire-runtime.api @@ -386,6 +386,9 @@ public final class com/squareup/wire/Wire { public static final fun get (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; } +public abstract interface annotation class com/squareup/wire/WireEnclosingType : java/lang/annotation/Annotation { +} + public abstract interface class com/squareup/wire/WireEnum { public abstract fun getValue ()I } diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/WireEnclosingType.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/WireEnclosingType.kt new file mode 100644 index 0000000000..c8bf9ef7cb --- /dev/null +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/WireEnclosingType.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.squareup.wire + +/** + * When the enclosing message is pruned, Wire still generates a class for that enclosing message to + * preserve the class names and structure. + * + * This marks such messages and is intended for use by tools like R8 to preserve them. + * + * https://developer.android.com/build/shrink-code + */ +annotation class WireEnclosingType diff --git a/wire-tests/src/jvmJavaPrunedTest/proto-java/com/squareup/wire/protos/roots/E.java b/wire-tests/src/jvmJavaPrunedTest/proto-java/com/squareup/wire/protos/roots/E.java index c7817c167b..0b232ad38d 100644 --- a/wire-tests/src/jvmJavaPrunedTest/proto-java/com/squareup/wire/protos/roots/E.java +++ b/wire-tests/src/jvmJavaPrunedTest/proto-java/com/squareup/wire/protos/roots/E.java @@ -9,6 +9,7 @@ import com.squareup.wire.ProtoWriter; import com.squareup.wire.ReverseProtoWriter; import com.squareup.wire.Syntax; +import com.squareup.wire.WireEnclosingType; import com.squareup.wire.WireField; import com.squareup.wire.internal.Internal; import java.io.IOException; @@ -23,6 +24,7 @@ /** * NOTE: This type only exists to maintain class structure for its nested types and is not an actual message. */ +@WireEnclosingType public final class E { private E() { throw new AssertionError();