Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type error using generics under java 8, but not java 7

I have a piece of code which compiles fine under java 7, but not under java 8. Here is a self-contained reproducing example (I've taken real code that exhibits this problem and stubbed out all the implementations):

import java.util.Iterator;

class ASTNode<T extends ASTNode> implements Iterable<T> {
  @Override public Iterator<T> iterator() { return null; }
}

class List<T extends ASTNode> extends ASTNode<T> {}

interface Function<F, T> {}

class Iterables {
  public static <F,T> Iterable<T> transform(
      Iterable<F> fromIterable, Function<? super F, ? extends T> function) { return null; }
}

class AstFunctions {
  public static <T extends ASTNode<?>> Function<T, String> prettyPrint() { return null; }
}

public class Main {
  public static void test() {
    List<? extends ASTNode<?>> list = null;
    Iterables.transform(list, AstFunctions.prettyPrint());
  }
}

Witness:

$ javac -version
javac 1.8.0_05

$ javac -source 1.7 Main.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning

$ javac -source 1.8 Main.java
Main.java:23: error: method transform in class Iterables cannot be applied to given types;
    Iterables.transform(list, AstFunctions.prettyPrint());
             ^
  required: Iterable<F>,Function<? super F,? extends T#1>
  found: List<CAP#1>,Function<ASTNode<?>,String>
  reason: cannot infer type-variable(s) F,T#1,T#2
    (argument mismatch; Function<CAP#1,String> cannot be converted to Function<? super CAP#1,? extends String>)
  where F,T#1,T#2 are type-variables:
    F extends Object declared in method <F,T#1>transform(Iterable<F>,Function<? super F,? extends T#1>)
    T#1 extends Object declared in method <F,T#1>transform(Iterable<F>,Function<? super F,? extends T#1>)
    T#2 extends ASTNode<?> declared in method <T#2>prettyPrint()
  where CAP#1 is a fresh type-variable:
    CAP#1 extends ASTNode<?> from capture of ? extends ASTNode<?>
1 error

(Perhaps notable is that Eclipse, configured for 1.8 compatibility, has no issues with this code).

Is this a compiler bug? If not, then assuming I'm allowed to change AstFunctions and Main (but not ASTNode, List, Function, or Iterables), how can I make this code compile? I'd also like to understand, if possible, what change to Java 8's type system makes this code not compile.

like image 706
Ismail Badawi Avatar asked May 22 '14 04:05

Ismail Badawi


2 Answers

This is not an issue in current version of the compiler at last javac 9. Any apparent bug should be checked against this version before any other consideration.

For last javac8 code the repository to check is last javac 8. This issue has already been fixed in that repository. The issue has been fixed with the patch for JDK-8033718

like image 63
Vicente Romero Avatar answered Oct 24 '22 23:10

Vicente Romero


UPDATE: see other answer - this was a bug in javac which has been fixed.


It feels like this should not compile and that Java 8 is exhibiting the correct behaviour:

  • Iterables.transform expects an Iterable<F> fromIterable and Function<? super F..., so the first generic type of Function needs to be a super class of the generic type of the Iterable
  • in your main, the type of the Iterable is F1 == ? extends ASTNode<?> and the first type of the Function returned by prettyPrint is F2 == T extends ASTNode<?>

I don't think there is a way to prove that F2 is a supertype of F1. For example let's say you have:

class A1 extends ASTNode<A1> {}
class A2 extends ASTNode<A2> {}

and in your main:

List<? extends ASTNode<?>> list = new List<A1>(); //F1 = A1

You could imagine that prettyPrint returns a Function<A2, String> (i.e. F2 = A2) and A2 is not a super class of A1.

So I believe the compile error makes sense. But I won't try to prove it based on the specifications because that would eat most of my day!

like image 40
assylias Avatar answered Oct 24 '22 22:10

assylias