Java 8 code instrumented by Clover fails to compile

お困りですか?

アトラシアン コミュニティをご利用ください。

コミュニティに質問

症状

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>

 

原因

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)

 

ソリューション

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"

Ant - build.xml
 <clover-setup instrumentLambda="block"/>
Maven - pom.xml
<configuration>
   <instrumentLambda>block</instrumentLambda>
</configuration>
Grails - BuildConfig.groovy
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

 

最終更新日: 2016 年 1 月 15 日

この内容はお役に立ちましたか?

はい
いいえ
この記事についてのフィードバックを送信する
Powered by Confluence and Scroll Viewport.