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
-
Instrumentation of expression-like lambdas with no compilation errors in all cases
Open
CLOV-1596
-
Use heuristics to solve JDK8 Stream compilation errors
Closed
CLOV-1762
-
Instrumentation of method references with no compilation errors
Open