Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eclipse fails to compile generic code, but mvn compile works

In Eclipse Kepler 4.2 with jdk 1.7 I get following error in Eclipse:

The method or(capture#2-of ?) in the type Optional<capture#2-of ?> is not applicable for the arguments (Object)

whereas it compiles successfully when running mvn compile.

The class looks following:

package testit;

import java.util.Map;
import java.util.Map.Entry;

import com.google.common.base.Optional;

public class Test {

    private static final Object NO_VALUE = new Object();

    public void method(Map<String, ?> map) {
        for (Entry<String, ?> entry : map.entrySet()) {
            Optional.fromNullable(entry.getValue()).or(NO_VALUE);
//                                                  ^^ error here
        }
    }
}

pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>my.test</groupId>
  <artifactId>testit</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
      <dependency>
          <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>13.0.1</version>
      </dependency>
  </dependencies>
  <build>
    <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>    
    </plugins>
  </build>
</project>

The similar code runs on production for a long while without errors (in this area). It is compiling in Maven, Jenkins, Intelij but not in Eclipse. Question is why it does not compile in Eclipse ?

like image 521
kubagruszka Avatar asked Apr 11 '14 07:04

kubagruszka


3 Answers

After a deep analysis by the Eclipse Team (btw great thanks!) their answer on this problem is following

Quoted after Stephan Herrmann from Eclipse Bugzilla:

This line doesn't apply for the following reason:

Type inference starts by resolving this expression:

Optional.fromNullable(entry.getValue())

In this expression all elements can be typed as stated above:

entry : Entry entry.getValue:
capture#2-of ? fromNullable(..) : (capture#2-of ?) -> Optional<capture#2-of ?>

In short: T is inferred to be "capture#2-of ?". Inference succeeds.

Only after all these have been decided, does resolution proceed to inspect the .or(..) method call. No reason to fill in un-inferred inference variables with "Object".

With a receiver of type Optional, we have these three overloads of "or":

Optional or(Optional)
capture#2-of ? or(Supplier<? extends capture#2-of ?> supplier)
capture#2-of ? or(capture#2-of ? defaultValue)

None of these methods is applicable for an argument of type Object. ecj arbitrarily picks the first method for error reporting:

"The method or(Optional) in the type Optional is not applicable for the arguments (Object)"

The difference between ecj and javac is most certainly covered by the mentioned javac bug: https://bugs.openjdk.java.net/browse/JDK-8016207

As mentioned in that bug, a future update of the JLS may possibly adopt parts of the current javac behavior. Until such a spec update is released, the only reliable point of reference for ecj is the JLS. In this specification I see no reason for changing the behavior of ecj in this regard.

BTW, the canonical fix (no cast needed) is:

Optional.<Object>fromNullable(entry.getValue()).or(NO_VALUE);

For now that would be the conclusion.

like image 100
kubagruszka Avatar answered Oct 05 '22 23:10

kubagruszka


Eclipse compiler is not the standard javac. It uses the ECJ Compiler This is definitely a bug in the Eclipse Compiler, as maven uses the standard javac to compile that's why it's working.

like image 23
Eugene Avatar answered Oct 05 '22 22:10

Eugene


You didn't state a question so I assume the question: "Who is right, and how to make the code work?"

I'd say Eclipse is right.

The result of fromNullable has Type parameter ?, i.e. there exists a type T but we don't know it.

The Optional returned by or must have the same type parameter as the Optional it is called on, which also means it takes the same unknown type T as a parameter. But Object might or might not be compatible to that type, so it is correct to fail.

In order to fix it, I think the following change should work:

Optional.fromNullable((Object)entry.getValue()).or(NO_VALUE);

The cast binds the otherwise unknown type parameter to Object which obviously is compatible to Object in the call to or.

like image 40
Jens Schauder Avatar answered Oct 06 '22 00:10

Jens Schauder