[Metadata] Add tests for missing rewrite of context receivers
Bug: b/266396725
Change-Id: Ifd02bd5ca3a823dc96358a33f79086cf1fa97b62
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index 36f3b07..259f1a0 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.MAX_SUPPORTED_VERSION;
import static com.android.tools.r8.ToolHelper.isWindows;
+import static com.google.common.io.Files.getNameWithoutExtension;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -223,6 +224,10 @@
return this;
}
+ public KotlinCompilerTool enableExperimentalContextReceivers() {
+ return addArguments("-Xcontext-receivers");
+ }
+
public KotlinCompilerTool addSourceFiles(Path... files) {
return addSourceFiles(Arrays.asList(files));
}
@@ -268,7 +273,8 @@
try {
// The Kotlin compiler does not require particular naming of files except for
// the extension, so just create a file called source.kt in a new directory.
- Path fileNamedKt = temp.newFolder().toPath().resolve("source.kt");
+ String newFileName = getNameWithoutExtension(fileNotNamedKt.toString()) + ".kt";
+ Path fileNamedKt = temp.newFolder().toPath().resolve(newFileName);
Files.copy(fileNotNamedKt, fileNamedKt);
return fileNamedKt;
} catch (IOException e) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java
new file mode 100644
index 0000000..206b7c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteContextReceiverTest.java
@@ -0,0 +1,189 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_8_0;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion.JAVA_8;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteContextReceiverTest extends KotlinMetadataTestBase {
+
+ private static final String PKG_LIB = PKG + ".context_receiver_lib";
+ private static final String PKG_APP = PKG + ".context_receiver_app";
+ private static final Path LIB_FILE =
+ getFileInTest(PKG_PREFIX + "/context_receiver_lib", "lib.txt");
+ private static final Path MAIN_FILE =
+ getFileInTest(PKG_PREFIX + "/context_receiver_app", "main.txt");
+ private static final String MAIN = PKG_APP + ".MainKt";
+ private final TestParameters parameters;
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "FooImpl::m1",
+ "BarImpl::m2",
+ "BazImpl::m3",
+ "BazImpl::m3",
+ "FooImpl::m1",
+ "BarImpl::m2",
+ "Hello World!");
+
+ @Parameterized.Parameters(name = "{0}, {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(),
+ getKotlinTestParameters()
+ .withCompilersStartingFromIncluding(KOTLINC_1_8_0)
+ .withOldCompilersStartingFrom(KOTLINC_1_8_0)
+ .withTargetVersion(JAVA_8)
+ .build());
+ }
+
+ public MetadataRewriteContextReceiverTest(
+ TestParameters parameters, KotlinTestParameters kotlinParameters) {
+ super(kotlinParameters);
+ this.parameters = parameters;
+ }
+
+ private static final KotlinCompileMemoizer libJars =
+ getCompileMemoizer()
+ .configure(
+ kotlinc ->
+ kotlinc
+ .addSourceFilesWithNonKtExtension(getStaticTemp(), LIB_FILE)
+ .enableExperimentalContextReceivers());
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+ .addSourceFilesWithNonKtExtension(temp, MAIN_FILE)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ kotlinc.getKotlinStdlibJar(),
+ kotlinc.getKotlinReflectJar(),
+ libJars.getForConfiguration(kotlinc, targetVersion))
+ .addClasspath(output)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForCompilationWithKeepAll() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(
+ libJars.getForConfiguration(kotlinc, targetVersion), kotlinc.getKotlinStdlibJar())
+ .addKeepClassAndMembersRules(PKG_LIB + ".*")
+ .addKeepAttributes(
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .addKeepKotlinMetadata()
+ .allowDiagnosticWarningMessages()
+ .addOptionsModification(
+ options -> options.testing.keepMetadataInR8IfNotRewritten = false)
+ .compile()
+ // TODO(b/266396725): Assert equivalence of metadata.
+ .writeToZip();
+ Path main =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFilesWithNonKtExtension(temp, MAIN_FILE)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar)
+ .addClasspath(main)
+ .run(parameters.getRuntime(), MAIN)
+ // TODO(b/266396725): Rewrite context receivers in metadata.
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
+ @Test
+ public void testMetadataInExtensionFunction_renamedKotlinSources() throws Exception {
+ R8TestCompileResult r8LibraryResult =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinAnnotationJar())
+ .addProgramFiles(libJars.getForConfiguration(kotlinc, targetVersion))
+ // Ensure that we do not rename members
+ .addKeepRules("-keepclassmembers class * { *; }")
+ // Keep the Foo class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Foo")
+ // Keep the Bar class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Bar")
+ // Keep the Baz class but rename it.
+ .addKeepRules("-keep,allowobfuscation class " + PKG_LIB + ".Baz")
+ // Keep all Printer fields.
+ .addKeepRules("-keep class " + PKG_LIB + ".Printer { *; }")
+ // Keep Super, but allow minification.
+ .addKeepRules("-keep class " + PKG_LIB + ".LibKt { <methods>; }")
+ .addKeepKotlinMetadata()
+ .compile();
+
+ // Rewrite the kotlin source to rewrite the classes from the mapping file
+ String kotlinSource = FileUtils.readTextFile(MAIN_FILE, StandardCharsets.UTF_8);
+
+ CodeInspector inspector = r8LibraryResult.inspector();
+ // Rewrite the source kotlin file that reference the renamed classes uses in the context
+ // receivers.
+ for (String className : new String[] {"Foo", "Bar", "Baz"}) {
+ String originalClassName = PKG_LIB + "." + className;
+ ClassSubject clazz = inspector.clazz(originalClassName);
+ assertThat(clazz, isPresentAndRenamed());
+ kotlinSource =
+ kotlinSource.replace("import " + originalClassName, "import " + clazz.getFinalName());
+ kotlinSource =
+ kotlinSource.replace(
+ ": " + className + " {",
+ ": "
+ + DescriptorUtils.getSimpleClassNameFromDescriptor(clazz.getFinalDescriptor())
+ + " {");
+ }
+
+ Path newSource = temp.newFolder().toPath().resolve("main.kt");
+ Files.write(newSource, kotlinSource.getBytes(StandardCharsets.UTF_8));
+
+ Path libJar = r8LibraryResult.writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(newSource)
+ .setOutputPath(temp.newFolder().toPath())
+ .enableExperimentalContextReceivers()
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(kotlinc.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), MAIN)
+ // TODO(b/266396725): Rewrite context receivers in metadata.
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt
new file mode 100644
index 0000000..9380c6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_app/main.txt
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.context_receiver_app
+
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Bar
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Foo
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Baz
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.Printer
+import com.android.tools.r8.kotlin.metadata.context_receiver_lib.callFooBar
+
+class FooImpl : Foo {
+
+ override fun m1(): String {
+ println("FooImpl::m1")
+ return "Hello "
+ }
+}
+
+class BarImpl : Bar {
+ override fun m2(): String {
+ println("BarImpl::m2")
+ return "World!"
+ }
+}
+
+class BazImpl : Baz {
+ override fun m3(): String {
+ println("BazImpl::m3")
+ return "BazImpl::m3"
+ }
+}
+
+fun main() {
+ with (FooImpl()) {
+ with (BarImpl()) {
+ val printer = Printer()
+ printer.fooBar()
+ with (BazImpl()) {
+ println(printer.getValue)
+ println(callFooBar())
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt
new file mode 100644
index 0000000..0235848
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/context_receiver_lib/lib.txt
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.context_receiver_lib
+
+interface Foo {
+ fun m1(): String
+}
+
+interface Bar {
+ fun m2(): String
+}
+
+interface Baz {
+ fun m3(): String
+}
+
+context(Foo, Bar)
+class Printer {
+
+ fun fooBar() {
+ m1();
+ m2();
+ }
+
+ context(Baz)
+ val getValue: String
+ get() = if (System.currentTimeMillis() == 0L) "foo" else m3()
+}
+
+context(Foo, Bar)
+fun callFooBar() : String {
+ return m1() + m2();
+}
\ No newline at end of file