Java 8 code instrumented by Clover fails to compile
Symptoms
Compilation of Java 8 code instrumented by Clover fails when a lambda expression is passed as an argument of a generic method.
Example #1 - overloaded generic methods
For the following code
public class LambdaAndGenerics {
static interface Predicate<T> {
boolean test(T t);
}
static class Fails {
void start() {
System.out.println(goo(e -> false)); // COMPILER ERRORS "reference to goo is ambiguous" + "cannot infer type variables"
}
<N> String goo(Class<N> arg) {
return "Fails class: " + arg;
}
<M> String goo(Predicate<M> arg) {
return "Fails predicate: " + arg.test(null);
}
}
public static void main(String[] args) {
new Fails().start();
}
}
compiler fails with:
java: reference to goo is ambiguous
both method <N>goo(java.lang.Class<N>) in LambdaAndGenerics.Fails and method <M>goo(LambdaAndGenerics.Predicate<M>) in LambdaAndGenerics.Fails match
java: incompatible types: cannot infer type-variable(s) I,T
(argument mismatch; java.lang.Class is not a functional interface)
Example #2 - Java 8 streams
The following code:
public static List<String> testMapAndCollectBounds(List<String> input) {
return input.stream()
.map(e -> e.toUpperCase())
.collect(Collectors.toList());
}
fails with a compilation error:
incompatible types: inference variable T has incompatible bounds
equality constraints: java.lang.String
lower bounds: java.lang.Object
The following code:
public static Stream<String> testMapAndFilterBounds(List<String> input) {
return input.stream()
.map(e -> e.toUpperCase())
.filter(e -> !e.isEmpty());
}
fails with a compilation error:
incompatible types: java.util.stream.Stream<java.lang.Object> cannot be converted to java.util.stream.Stream<java.lang.String>
Cause
In case of example #1 it's a bug (aka technical limitation) in Oracle's javac compiler which fails to perform type inference for several nested generic types or methods. Roughly speaking, due to performance reasons, a compiler performs simplified method matching:
- in a first step it tries to match all methods with a given name, no matter of their signature to the lambda expression (i.e. it ignores overloading)
- in a second step it selects a method which fits the best (i.e. taking the one with the most narrow type)
In means that a compiler fails if you have two (or more) overloaded methods and one (or more) of them does have a functional interface argument, while others don't have.
In case of example #2 this relates with a fact how type of a lambda expression or type of a method reference are being resolved by the compiler. See Java Language Specification, chapters "15.13 Method Reference Expressions" and "15.27 Lambda Expressions" for more details. Due to a fact that an original lambda is being wrapped into Clover's helper method having a signature like this:
<I, T extends I> I lambdaInc(int index, T lambda, int stmtIndex)
this may cause problems when used with generic method having wildcards, such as:
stream()
.map(...) // <R> Stream<R> map(Function<? super T, ? extends R> mapper)
.collect(...) // <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
Resolution
1) Change a lambda function from an expression to a code block, e.g.:
// expression lambda
System.out.println(goo(e -> false));
// block lambda
System.out.println(goo(e -> { return false; }));
Clover uses different instrumentation for these two lambda forms, so a type inference error will not occur for a lambda block.
2) Or disable instrumentation of lambda functions declared as expressions (since Clover 3.2.2).
Use instrumentLambda = "all_but_reference", "block" or "none"
<clover-setup instrumentLambda="block"/>
<configuration>
<instrumentLambda>block</instrumentLambda>
</configuration>
clover {
instrumentLambda = "block"
}
Eclipse:
Open "Project Properties "> "Clover" page > "Instrumentation" tab. In the "Miscellaneous" box select proper value in "Instrument lambda functions" drop-down.
IntelliJ IDEA:
Open "File" > "Settings" > "Project Settings" > "Clover" page > "Compilation" tab. In the "Miscellaneous" box select proper value in "Instrument lambda functions" drop-down.
3) Or surround a lambda by ///CLOVER:OFF and ///CLOVER:ON comments, e.g.:
... some code ...
///CLOVER:OFF
System.out.println(goo(e -> false));
///CLOVER:ON
... some code ...
4) Or rename a method which does not have an argument of functional interface type (example #1), e.g.:
static class Works {
void start() {
System.out.println(goo(e -> false));
}
<M> String goo(Predicate<M> arg) {
return "goo: " + arg.test(null);
}
<M> String goo(SomeFunctionalInterface<M> arg) {
return "goo: " + arg.run(null);
}
<N> String notAGoo(Class<N> arg) { // this is not a functional interface
return "Class: " + arg;
}
}
5) Since Clover 4.0.5 heuristics are applied to rewrite lambda expressions in JDK Stream's and Guava classes' methods into a block form. More details here: Lambda rewriting heuristics. Try upgrading to Clover 4.0.5 and/or use instrumentLambda="expression"
or instrumentLambda="all_but_references"
.
Bug tracker
CLOV-1465 - Getting issue details... STATUS
CLOV-1596 - Getting issue details... STATUS
CLOV-1762 - Getting issue details... STATUS