Support Duration#isPositive
- Always supported with desugared libraryi (except minimal)
- Supported above or equal api 26
Fixes: b/389751852
Change-Id: I9f629b381d8ad444490511bed0d613738c522c9a
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 4d8c7c5..5ec1ce3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -502,6 +502,7 @@
public final DexType mapEntryType = createStaticallyKnownType(mapEntryDescriptor);
public final DexType abstractMapSimpleEntryType =
createStaticallyKnownType("Ljava/util/AbstractMap$SimpleEntry;");
+ public final DexType durationType = createStaticallyKnownType("Ljava/time/Duration;");
public final DexType collectionType = createStaticallyKnownType(collectionDescriptor);
public final DexType comparatorType = createStaticallyKnownType(comparatorDescriptor);
public final DexType callableType = createStaticallyKnownType(callableDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index d7aff3c..048d9a3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -364,6 +364,9 @@
if (options.getMinApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
initializeAndroidBaklavaMethodProviders(factory);
}
+ if (typeIsPresent(factory.durationType)) {
+ initializeDurationMethodProviders(factory);
+ }
}
private Map<DexType, AndroidApiLevel> initializeTypeMinApi(DexItemFactory factory) {
@@ -374,6 +377,7 @@
builder.put(factory.javaUtilSetType, AndroidApiLevel.B);
builder.put(factory.streamType, AndroidApiLevel.N);
builder.put(factory.supplierType, AndroidApiLevel.N);
+ builder.put(factory.durationType, AndroidApiLevel.O);
ImmutableMap<DexType, AndroidApiLevel> typeMinApi = builder.build();
assert minApiMatchDatabaseMinApi(typeMinApi);
return typeMinApi;
@@ -1777,6 +1781,17 @@
}
}
+ private void initializeDurationMethodProviders(DexItemFactory factory) {
+ // boolean Duration.isPositive()
+ DexType type = factory.durationType;
+ DexString name = factory.createString("isPositive");
+ DexProto proto = factory.createProto(factory.booleanType);
+ DexMethod method = factory.createMethod(type, proto, name);
+ addProvider(
+ new StatifyingMethodGenerator(
+ method, BackportedMethods::DurationMethods_isPositive, "isPositive", type));
+ }
+
private void initializeAndroidBaklavaMethodProviders(DexItemFactory factory) {
DexType type;
DexString name;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 6c3f005..6aca418 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -85,6 +85,7 @@
factory.createSynthesizedType("Ljava/lang/reflect/Method;");
factory.createSynthesizedType("Ljava/math/BigDecimal;");
factory.createSynthesizedType("Ljava/math/BigInteger;");
+ factory.createSynthesizedType("Ljava/time/Duration;");
factory.createSynthesizedType("Ljava/util/AbstractMap$SimpleImmutableEntry;");
factory.createSynthesizedType("Ljava/util/ArrayList;");
factory.createSynthesizedType("Ljava/util/Arrays;");
@@ -2413,6 +2414,61 @@
ImmutableList.of());
}
+ public static CfCode DurationMethods_isPositive(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/time/Duration;"),
+ factory.createProto(factory.booleanType),
+ factory.createString("isZero")),
+ false),
+ new CfIf(IfType.NE, ValueType.INT, label1),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ factory.createMethod(
+ factory.createType("Ljava/time/Duration;"),
+ factory.createProto(factory.booleanType),
+ factory.createString("isNegative")),
+ false),
+ new CfIf(IfType.NE, ValueType.INT, label1),
+ new CfConstNumber(1, ValueType.INT),
+ new CfGoto(label2),
+ label1,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {
+ FrameType.initializedNonNullReference(
+ factory.createType("Ljava/time/Duration;"))
+ })),
+ new CfConstNumber(0, ValueType.INT),
+ label2,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {
+ FrameType.initializedNonNullReference(
+ factory.createType("Ljava/time/Duration;"))
+ }),
+ new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
+ new CfReturn(ValueType.INT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode ExecutorServiceMethods_closeExecutorService(
DexItemFactory factory, DexMethod method) {
CfLabel label0 = new CfLabel();
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/DurationMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/DurationMethods.java
new file mode 100644
index 0000000..6aeb796
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/DurationMethods.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2025, 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.ir.desugar.backports;
+
+import java.time.Duration;
+
+public class DurationMethods {
+
+ public static boolean isPositive(Duration duration) {
+ return !duration.isZero() && !duration.isNegative();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index 40d6b6d..100b640 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -50,6 +50,7 @@
CollectionMethods.class,
CollectionsMethods.class,
DoubleMethods.class,
+ DurationMethods.class,
ExecutorServiceMethods.class,
FloatMethods.class,
IntegerMethods.class,
diff --git a/src/test/java21/com/android/tools/r8/jdk21/desugaredlibrary/DurationIsPositiveTest.java b/src/test/java21/com/android/tools/r8/jdk21/desugaredlibrary/DurationIsPositiveTest.java
new file mode 100644
index 0000000..b5c6800
--- /dev/null
+++ b/src/test/java21/com/android/tools/r8/jdk21/desugaredlibrary/DurationIsPositiveTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2025, 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.jdk21.desugaredlibrary;
+
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.time.Duration;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DurationIsPositiveTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED_RESULT = StringUtils.lines("true", "false", "false");
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimesAndAllApiLevels().build(),
+ ImmutableList.of(JDK11, JDK11_PATH),
+ DEFAULT_SPECIFICATIONS);
+ }
+
+ public DurationIsPositiveTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ Assume.assumeTrue(
+ "Run only once",
+ libraryDesugaringSpecification == JDK11 && compilationSpecification == D8_L8DEBUG);
+ testForD8()
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Executor.class)
+ .applyIf(
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O),
+ b -> b.assertSuccessWithOutput(EXPECTED_RESULT),
+ parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V8_1_0),
+ b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testDesugaredLib() throws Exception {
+ testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .addKeepMainRule(Executor.class)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ static class Executor {
+ public static void main(String[] args) {
+ System.out.println(Duration.ofDays(5).isPositive());
+ System.out.println(Duration.ofDays(-3).isPositive());
+ System.out.println(Duration.ofDays(0).isPositive());
+ }
+ }
+}