Warnings in wrapper generation
- Warning for large wrappers (Stream, ...)
- Warning for final methods (SummaryStatistics, ...)
- Error for final classes (Does not happen in practice AFAIK)
- Stream wrappers working (since final methods do not crash anymore)
Bug: 134732760
Change-Id: I9b8d7fe9aebb81a76c4cafac889f903eb0781c04
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 1f9c023..3bdaf06 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -185,6 +185,7 @@
public final DexString invokeMethodName = createString("invoke");
public final DexString invokeExactMethodName = createString("invokeExact");
+ public final DexString runtimeExceptionDescriptor = createString("Ljava/lang/RuntimeException;");
public final DexString assertionErrorDescriptor = createString("Ljava/lang/AssertionError;");
public final DexString charSequenceDescriptor = createString("Ljava/lang/CharSequence;");
public final DexString charSequenceArrayDescriptor = createString("[Ljava/lang/CharSequence;");
@@ -325,6 +326,7 @@
public final DexType runnableType = createType(runnableDescriptor);
public final DexType optionalType = createType(optionalDescriptor);
+ public final DexType runtimeExceptionType = createType(runtimeExceptionDescriptor);
public final DexType throwableType = createType(throwableDescriptor);
public final DexType illegalAccessErrorType = createType(illegalAccessErrorDescriptor);
public final DexType icceType = createType(icceDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 036e8b4..a9c70ca 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
@@ -33,8 +34,10 @@
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -99,6 +102,11 @@
new ConcurrentHashMap<>();
private final Map<DexType, Pair<DexType, DexProgramClass>> vivifiedTypeWrappers =
new ConcurrentHashMap<>();
+ // The invalidWrappers are wrappers with incorrect behavior because of final methods that could
+ // not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
+ // not raise explicit errors. So we register them here and conversion methods for such wrappers
+ // raise a runtime exception instead of generating the wrapper.
+ private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
private final Set<DexType> generatedWrappers = Sets.newConcurrentHashSet();
private final DexItemFactory factory;
private final DesugaredLibraryAPIConverter converter;
@@ -166,6 +174,16 @@
DexClass dexClass = appView.definitionFor(type);
// The dexClass should be a library class, so it cannot be null.
assert dexClass != null && dexClass.isLibraryClass();
+ if (dexClass.accessFlags.isFinal()) {
+ throw appView
+ .options()
+ .reporter
+ .fatalError(
+ new StringDiagnostic(
+ "Cannot generate a wrapper for final class "
+ + dexClass.type
+ + ". Add a custom conversion in the desugared library."));
+ }
pair.setSecond(wrapperGenerator.apply(dexClass, pair.getFirst()));
}
return pair.getFirst();
@@ -239,6 +257,7 @@
// v2 <- convertTypeToVivifiedType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
+ Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
assert holderClass != null;
@@ -247,19 +266,26 @@
wrapperField.field.holder,
dexEncodedMethod.method.proto,
dexEncodedMethod.method.name);
- CfCode cfCode =
- new APIConverterVivifiedWrapperCfCodeProvider(
- appView,
- methodToInstall,
- wrapperField.field,
- converter,
- holderClass.isInterface())
- .generateCfCode();
+ CfCode cfCode;
+ if (dexEncodedMethod.isFinal()) {
+ invalidWrappers.add(wrapperField.field.holder);
+ finalMethods.add(dexEncodedMethod.method);
+ continue;
+ } else {
+ cfCode =
+ new APIConverterVivifiedWrapperCfCodeProvider(
+ appView,
+ methodToInstall,
+ wrapperField.field,
+ converter,
+ holderClass.isInterface())
+ .generateCfCode();
+ }
DexEncodedMethod newDexEncodedMethod =
newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
- return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ return finalizeWrapperMethods(generatedMethods, finalMethods);
}
private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
@@ -276,26 +302,58 @@
// v2 <- convertVivifiedTypeToType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
+ Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
for (DexEncodedMethod dexEncodedMethod : dexMethods) {
DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
assert holderClass != null;
DexMethod methodToInstall =
converter.methodWithVivifiedTypeInSignature(
dexEncodedMethod.method, wrapperField.field.holder);
- CfCode cfCode =
- new APIConverterWrapperCfCodeProvider(
- appView,
- dexEncodedMethod.method,
- wrapperField.field,
- converter,
- holderClass.isInterface())
- .generateCfCode();
-
+ CfCode cfCode;
+ if (dexEncodedMethod.isFinal()) {
+ invalidWrappers.add(wrapperField.field.holder);
+ finalMethods.add(dexEncodedMethod.method);
+ continue;
+ } else {
+ cfCode =
+ new APIConverterWrapperCfCodeProvider(
+ appView,
+ dexEncodedMethod.method,
+ wrapperField.field,
+ converter,
+ holderClass.isInterface())
+ .generateCfCode();
+ }
DexEncodedMethod newDexEncodedMethod =
newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
- return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ return finalizeWrapperMethods(generatedMethods, finalMethods);
+ }
+
+ private DexEncodedMethod[] finalizeWrapperMethods(
+ List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
+ if (finalMethods.isEmpty()) {
+ return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+ }
+ // Wrapper is invalid, no need to add the virtual methods.
+ reportFinalMethodsInWrapper(finalMethods);
+ return DexEncodedMethod.EMPTY_ARRAY;
+ }
+
+ private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
+ String[] methodArray =
+ methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
+ appView
+ .options()
+ .reporter
+ .warning(
+ new StringDiagnostic(
+ "Desugared library API conversion: cannot wrap final methods "
+ + Arrays.toString(methodArray)
+ + ". "
+ + methods.iterator().next().holder
+ + " is marked as invalid and will throw a runtime exception upon conversion."));
}
DexEncodedMethod newSynthesizedMethod(
@@ -346,6 +404,17 @@
workList.add(superClass);
}
}
+ // 10 is large enough to avoid warnings on Clock/Function, but not on Stream.
+ if (implementedMethods.size() > 10) {
+ appView
+ .options()
+ .reporter
+ .warning(
+ new StringDiagnostic(
+ "Desugared library API conversion: Generating a large wrapper for "
+ + libraryClass.type
+ + ". Is that the intended behavior?"));
+ }
return implementedMethods;
}
@@ -439,6 +508,7 @@
synthesizeConversionMethod(
synthesizedClass.type,
type,
+ type,
converter.vivifiedTypeFor(type),
reverse == null ? null : reverse.getSecond()));
}
@@ -448,28 +518,45 @@
synthesizedClass.addDirectMethod(
synthesizeConversionMethod(
synthesizedClass.type,
+ type,
converter.vivifiedTypeFor(type),
type,
reverse == null ? null : reverse.getSecond()));
}
private DexEncodedMethod synthesizeConversionMethod(
- DexType holder, DexType argType, DexType returnType, DexClass reverseWrapperClassOrNull) {
+ DexType holder,
+ DexType type,
+ DexType argType,
+ DexType returnType,
+ DexClass reverseWrapperClassOrNull) {
DexMethod method =
factory.createMethod(
holder, factory.createProto(returnType, argType), factory.convertMethodName);
-
- DexField uniqueFieldOrNull =
- reverseWrapperClassOrNull == null
- ? null
- : reverseWrapperClassOrNull.instanceFields().get(0).field;
- CfCode cfCode =
- new APIConverterWrapperConversionCfCodeProvider(
- appView,
- argType,
- uniqueFieldOrNull,
- factory.createField(holder, returnType, factory.wrapperFieldName))
- .generateCfCode();
+ CfCode cfCode;
+ if (invalidWrappers.contains(holder)) {
+ cfCode =
+ new APIConverterThrowRuntimeExceptionCfCodeProvider(
+ appView,
+ factory.createString(
+ "Unsupported conversion for "
+ + type
+ + ". See compilation time warnings for more infos."),
+ holder)
+ .generateCfCode();
+ } else {
+ DexField uniqueFieldOrNull =
+ reverseWrapperClassOrNull == null
+ ? null
+ : reverseWrapperClassOrNull.instanceFields().get(0).field;
+ cfCode =
+ new APIConverterWrapperConversionCfCodeProvider(
+ appView,
+ argType,
+ uniqueFieldOrNull,
+ factory.createField(holder, returnType, factory.wrapperFieldName))
+ .generateCfCode();
+ }
return newSynthesizedMethod(
method,
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 2a1d196..60ca8ca 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.cf.code.CfInstanceOf;
@@ -17,6 +18,7 @@
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexField;
@@ -305,4 +307,34 @@
return standardCfCodeFromInstructions(instructions);
}
}
+
+ public static class APIConverterThrowRuntimeExceptionCfCodeProvider
+ extends SyntheticCfCodeProvider {
+ DexString message;
+
+ public APIConverterThrowRuntimeExceptionCfCodeProvider(
+ AppView<?> appView, DexString message, DexType holder) {
+ super(appView, holder);
+ this.message = message;
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ DexItemFactory factory = appView.dexItemFactory();
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfNew(factory.runtimeExceptionType));
+ instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
+ instructions.add(new CfConstString(message));
+ instructions.add(
+ new CfInvoke(
+ Opcodes.INVOKESPECIAL,
+ factory.createMethod(
+ factory.runtimeExceptionType,
+ factory.createProto(factory.voidType, factory.stringType),
+ factory.initMethodName),
+ false));
+ instructions.add(new CfThrow());
+ return standardCfCodeFromInstructions(instructions);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java
new file mode 100644
index 0000000..c2297ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.time.Year;
+import org.junit.Test;
+
+public class APIConversionFinalClassErrorTest extends APIConversionTestBase {
+
+ @Test
+ public void testFinalMethod() {
+ try {
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compileWithExpectedDiagnostics(this::assertDiagnosis);
+ fail("Expected compilation error");
+ } catch (CompilationFailedException ignored) {
+
+ }
+ }
+
+ private void assertDiagnosis(TestDiagnosticMessages d) {
+ assertEquals(
+ "Cannot generate a wrapper for final class java.time.Year."
+ + " Add a custom conversion in the desugared library.",
+ d.getErrors().get(0).getDiagnosticMessage());
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ System.out.println(CustomLibClass.call(Year.now()));
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ public static long call(Year year) {
+ return 0L;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
new file mode 100644
index 0000000..10b6d75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.LongSummaryStatistics;
+import org.junit.Test;
+
+public class APIConversionFinalWarningTest extends APIConversionTestBase {
+
+ @Test
+ public void testFinalMethod() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .assertWarningMessageThatMatches(
+ startsWith(
+ "Desugared library API conversion: cannot wrap final methods"
+ + " [java.util.LongSummaryStatistics"))
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+ .addRunClasspathFiles(customLib)
+ .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "Unsupported conversion for java.util.LongSummaryStatistics. See compilation time"
+ + " warnings for more infos."));
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ LongSummaryStatistics statistics = new LongSummaryStatistics();
+ statistics.accept(3L);
+ try {
+ makeCall(statistics);
+ } catch (RuntimeException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ static void makeCall(LongSummaryStatistics statistics) {
+ CustomLibClass.call(statistics);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ public static long call(LongSummaryStatistics stats) {
+ return stats.getMax();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
new file mode 100644
index 0000000..efbe9b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+public class APIConversionLargeWarningTest extends APIConversionTestBase {
+
+ @Test
+ public void testFinalMethod() throws Exception {
+ Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+ testForD8()
+ .setMinApi(AndroidApiLevel.B)
+ .addProgramClasses(Executor.class)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+ .compile()
+ .assertWarningMessageThatMatches(
+ startsWith(
+ "Desugared library API conversion: Generating a large wrapper for"
+ + " java.util.stream.Stream"))
+ .assertNoWarningMessageThatMatches(
+ startsWith(
+ "Desugared library API conversion: Generating a large wrapper for java.time.Clock"))
+ .assertNoWarningMessageThatMatches(
+ startsWith(
+ "Desugared library API conversion: Generating a large wrapper for"
+ + " java.util.function.Function"));
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ CustomLibClass.callClock(Clock.systemUTC());
+ CustomLibClass.callStream(Stream.empty());
+ CustomLibClass.callFunction(x -> x);
+ }
+ }
+
+ // This class will be put at compilation time as library and on the runtime class path.
+ // This class is convenient for easy testing. Each method plays the role of methods in the
+ // platform APIs for which argument/return values need conversion.
+ static class CustomLibClass {
+
+ public static void callStream(Stream stream) {}
+
+ public static void callClock(Clock clock) {}
+
+ public static void callFunction(Function<String, String> func) {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
index 4fe9a9d..e3e8c77 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
@@ -1,7 +1,6 @@
// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
import static org.hamcrest.CoreMatchers.endsWith;
@@ -14,7 +13,9 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import java.util.Arrays;
+import java.util.Random;
import java.util.function.IntUnaryOperator;
+import java.util.stream.IntStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -47,12 +48,13 @@
.assertNoWarningMessageThatMatches(containsString("java.util.Random#ints"))
.assertNoWarningMessageThatMatches(endsWith("is a desugared type)."))
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutput(StringUtils.lines("[5, 6, 7]"));
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "[5, 6, 7]", "java.util.stream.IntPipeline$Head", "IntSummaryStatistics"));
}
@Test
public void testAPIConversionDesugaring() throws Exception {
- // TODO(b/134732760): Make library API work when library desugaring is on for Stream.
testForD8()
.addInnerClasses(APIConversionTest.class)
.setMinApi(parameters.getApiLevel())
@@ -60,7 +62,12 @@
.compile()
.addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
.run(parameters.getRuntime(), Executor.class)
- .assertSuccessWithOutput(StringUtils.lines("[5, 6, 7]"));
+ .assertSuccessWithOutput(
+ StringUtils.lines(
+ "[5, 6, 7]",
+ "j$.util.stream.IntStream$-V-WRP",
+ "Unsupported conversion for java.util.IntSummaryStatistics. See compilation time"
+ + " warnings for more infos."));
}
static class Executor {
@@ -69,9 +76,31 @@
int[] ints = new int[3];
Arrays.setAll(ints, new MyFunction());
System.out.println(Arrays.toString(ints));
- // TODO(b/134732760): Support Stream wrappers.
- // IntStream intStream = new Random().ints();
- // System.out.println(intStream.getClass().getName());
+ IntStream intStream = new Random().ints();
+ System.out.println(intStream.getClass().getName());
+ CharSequence charSequence =
+ new CharSequence() {
+ @Override
+ public int length() {
+ return 1;
+ }
+
+ @Override
+ public char charAt(int index) {
+ return 42;
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ return null;
+ }
+ };
+ IntStream fixedSizedIntStream = charSequence.codePoints();
+ try {
+ System.out.println(fixedSizedIntStream.summaryStatistics().getClass().getSimpleName());
+ } catch (RuntimeException e) {
+ System.out.println(e.getMessage());
+ }
}
}