Bug 190945 - [1.5][compiler] failure to compile complex generic code
Summary: [1.5][compiler] failure to compile complex generic code
Status: VERIFIED WONTFIX
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.3   Edit
Hardware: All All
: P3 major (vote)
Target Milestone: 3.4 M1   Edit
Assignee: Philipe Mulet CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2007-06-05 00:52 EDT by Carl Quinn CLA
Modified: 2007-12-20 06:58 EST (History)
9 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Carl Quinn CLA 2007-06-05 00:52:02 EDT
Build ID: I20070601-1539

Steps To Reproduce:

The following stripped-down code snippet fails to compile in Eclipse 3.3RC3, but compiles fine with javac 1.5 & 1.6.


Error:

The method compound(Iterable<? extends Comparator<? super T>>) in the 
 type BugSample is not applicable for the arguments (List<Comparator<? 
 extends Object>>)


More information:

import java.util.Comparator;
import java.util.List;

public class BugSample {

  public static <T> Comparator<T> compound(
      Comparator<? super T> a,
      Comparator<? super T> b) {
    return compound(asList(a, b));
  }

  public static <T> Comparator<T> compound(
      Iterable<? extends Comparator<? super T>> comparators) {
    return null;
  }

  public static <E> List<E> asList(E a, E b) {
    return null;
  }

}
Comment 1 Olivier Thomann CLA 2007-06-05 08:54:02 EDT
Reproduced with HEAD + fixes for bug 189933 and bug 190748.
----------
1. ERROR in D:\tests_sources\X.java (at line 9)
	return compound(asList(a, b));
	       ^^^^^^^^
The method compound(Iterable<? extends Comparator<? super T>>) in the type X is not applicable for the arguments (List<Comparator<? extends Object>>)
----------
1 problem (1 error)
Comment 2 Olivier Thomann CLA 2007-06-05 09:52:27 EDT
Not a regression. 3.2.2 and 3.2.0 also fail to compile this code.
Comment 3 Kent Johnson CLA 2007-06-05 14:28:12 EDT
Since this case does not report a problem :

class BugSample {
 <T> Comparator<T> compound(Comparator<? super T> a, Comparator<? super T> b) {
  return compound(asList(a));
 }
 <T> Comparator<T> compound(Iterable<? extends Comparator<? super T>> c) {
  return null;
 }
 <E> List<E> asList(E a) {
  return null;
 }
}

It looks like we're having problems detecting type identity/compatibility b/w the parameter types for a & b.
Comment 4 Carl Quinn CLA 2007-06-05 14:39:58 EDT
I disagree about this not being a regression--I had reduced my snippet too much. 

In the complete context in our codebase at Google, the real problematic code does compile under 3.2.0. This slightly expanded snippet compiles under 3.2.0 but not 3.3RC3:

    import java.util.Comparator;
    import java.util.List;

    public class BugSample {

      public static <T> Comparator<T> compound(
          Comparator<? super T> a,
          Comparator<? super T> b,
          Comparator<? super T>... rest) {
        return compound(asList(a, b, rest));
      }

      public static <T> Comparator<T> compound(
          Iterable<? extends Comparator<? super T>> comparators) {
        return null;
      }

      public static <E> List<E> asList(E a, E b, E[] rest) {
        return null;
      }

    }

Comment 5 Kent Johnson CLA 2007-06-05 15:05:43 EDT
Olivier found that this new problem is caused by the fix for bug 188103
Comment 6 Olivier Thomann CLA 2007-06-05 15:51:29 EDT
With HEAD version, leastContainingInvocation(..) returns:
public interface Comparator<?>
and with v_763 it returns:
public interface Comparator<? super T>

It seems that the order in which the invocations are processed impact the result.

In v_763, the order for the invocations is:

public interface Comparator<? super T>
public interface Comparator<capture#1-of ? super T>
public interface Comparator<capture#2-of ? super T>

and in HEAD the order is:
public interface Comparator<capture#1-of ? super T>
public interface Comparator<capture#2-of ? super T>
public interface Comparator<? super T>

To prove that the process order is the problem here, I reverted the for loop to be:
		for (int i = invocations.length - 1; i >= 0; i--) {
instead of:
		for (int i = 0, length = invocations.length; i < length; i++) {

and then the test case compiles.
Comment 7 Olivier Thomann CLA 2007-06-05 15:55:43 EDT
The first test case still doesn't compile even if with RC1.
Comment 8 Philipe Mulet CLA 2007-06-06 05:17:29 EDT
I have an idea why the ordering is making a difference. Now, in theory we were weak before... the problem could have occurred before, and likely on a different VM (like Harmony) where ordering would have differed (see bug 188103).
If true, then this means we had a bug, which only recently made more proeminent. The real fix is elsewhere however (and I don't have it right now).

Comment 9 Kent Johnson CLA 2007-06-06 14:48:36 EDT
There are likely several unrelated bugs that cause these cases to fail, but here is one that may be the cause of several failures.

Start with this case which we compile fine:

class SingleParameter {
	<T> C<T> x(C<? super T> a, C<? super T> b) {
		return y(z(a));
	}

	// called with L<C<capture#1-of ? super T>> from result of z(a)
	<U> C<U> y(I<? extends C<? super U>> c) { return null; }

	// becomes L<C<capture#1-of ? super T>> z(C<capture#1-of ? super T>) 
	<E> L<E> z(E a) { return null; }
}

interface C<U1> {}
interface I<U2> {}
interface L<U3> extends I<U3> {}


But once we change it to:

class TwoParameters {
	<T> C<T> x(C<? super T> a, C<? super T> b) {
		return y(z(a, b));
	}

	// called with L<C<? extends Object>> from result of z(a, b)
	// L<C<? extends Object>> is NOT a match to I<? extends C<? super T>>
	// even though it should be
	<U> C<U> y(I<? extends C<? super U>> c) { return null; }

	// L<C<? extends Object>> z(C<? extends Object>, C<? extends Object>)
	<E> L<E> z(E a, E b) { return null; }
}


So we have a problem with this reduced case :

class ReducedCase {
	void test() {
		L<C<? extends Object>> l = null;
		y(l); // should be acceptable but fails to match
	}
	<U> void y(I<? extends C<? super U>> c) {}
}
Comment 10 Kent Johnson CLA 2007-06-06 16:15:11 EDT
We appear to have a problem with inferring the correct type :

class ReducedCase {
        void test() {
                L<C<? extends Object>> l = null;
                x(l); // should be acceptable, but we fail
                y(l); // cannot be applied
        }
        <U> void x(I<? extends C<? super U>> c) {}
        void y(I<? extends C<? super Object>> c) {}
}
Comment 11 Philipe Mulet CLA 2007-06-07 06:16:20 EDT
I also think there are several issues aggregated here.
I'd like to go through them carefully, and unfortunately this will likely not make it for 3.3RC4. Now, as soon as I have a patch, I would post it somewhere for you Carl to play with it. How does this sound ?
Comment 12 Philipe Mulet CLA 2007-06-07 10:06:26 EDT
Re: comment 1. 
Javac tolerates the code, but I think this is a bug in their land; according to the following analysis. Need to be confirmed (I ping'ed spec lead to find out).

I simplified the case to be, as the invocation of #asList(...) is not the issue here. Basically both compilers agree that in original example, the invocation of asList(a,b) returns List<Comparator<? extends Object>>
[for the record, lub(Comparator<capture-1-of ? super T>,Comparator<capture-2-of ? super T>) --> Comparator<? extends Object> - as per spec).

import java.util.Comparator;
import java.util.List;
class BugSample {
  void bar(List<Comparator<? extends Object>> list) {
	  compound(list);// (1) Eclipse rejects, Javac accepts
  } 
  public static <U> Comparator<U> compound(Iterable<? extends Comparator<? super U>> comparators) {
    return null;
  }
}

15.12.2.7 - inference from argument types

A=List<Comparator<? extends Object>>   <<   F=Iterable<? extends Comparator<? super U>>

    using top of page 454:
      if F has the form G<..., Yk-1, ? extends U, Yk+1, ...>,
      x G<..., Xk-1, V, Xk+1, ...>, where V is a type expression. Then this algorithm
        is applied recursively to the constraint V << U

        i.e. V=Comparator<? extends Object> << U=Comparator<? super U>

      back to A << F (recursively applying A=V << F=U)

A=Comparator<? extends Object> << F=Comparator<? super U>

    using top of page 455   
      if F has the form G<..., Yk-1, ? super U, Yk+1, ...>,
      &#10019; Otherwise, no constraint is implied on Tj.
      DISCUSSION
      Here, we have A = G<? extends V> << G<? super U>. In general, we cannot conclude
      anything in this case. However, it is not necessarily an error. It may be that U will eventually
      be inferred to the null type, in which case the call may indeed be valid. Therefore, we simply
      refrain from placing any constraint on U.

--> yields nothing

15.12.2.8 - inference using expected type (none here)
--> yields nothing

hence:
U is inferred by default to be: Object

Then need to check applicability to original arguments:

Comparator<Object> compound(Iterable<? extends Comparator<? super Object>>) 

applicable to: (List<Comparator<? extends Object>>)
i.e. using 4.10.2
[ARG-COMP] List<Comparator<? extends Object>>   <:   Iterable<? extends Comparator<? super Object>>

i.e. 
Iterable<Comparator<? extends Object>>   <:   Iterable<? extends Comparator<? super Object>>

only if arguments are contained as per 4.5.1.1

i.e.
Comparator<? extends Object>   <=   ? extends Comparator<? super Object>

This doesn't match any of the pattern described by the spec (p.55).
So no containment.

===> the generic invocation is not valid.



Note that you can also prove that arguments are incompatible (see line [ARG-COMP] above) by writing the following little program; and note that javac rejects it too.

import java.util.*;
class X {
  Iterable<Comparator<? extends Object>> itc1;
  Iterable<? extends Comparator<? super Object>> itc2 = itc1;
}
X.java:4: incompatible types
found   : java.lang.Iterable<java.util.Comparator<? extends java.lang.Object>>
required: java.lang.Iterable<? extends java.util.Comparator<? super java.lang.Object>>
  Iterable<? extends Comparator<? super Object>> c2 = c1;

Now, is this misinterpreted the spec somewhere ? Or does it smell like this is an inconsistency in Javac ?





Comment 13 Philipe Mulet CLA 2007-06-07 10:34:11 EDT
Added GenericTypeTest#test1142-1144 to cover these scenarii.
Comment 14 Philipe Mulet CLA 2007-06-07 11:00:43 EDT
Comment 3 is different in the sense that there is no lub of 2 different captures involved, and thus it surfaces the <capture1-of ? super T> which flows nicely in the inference from thereon. Both Javac and us agree there.

Added GenericTypeTest#test1145
Comment 15 Philipe Mulet CLA 2007-06-07 11:20:06 EDT
Onto comment 4. This one puzzles me.

Adding an array type in the process of lub computation, changes completely the end result.
e.g.
asList(a,b)      --> List<Comparator<? extends Object>>
asList(a,b,rest) --> List<Comparator<? super T>>

Trying to understand why. I think this is an over-simplification done in the compiler (both Javac and us), where the first bound is used where glb should be used. Hence, the ordering makes a difference.

Changing the order is not going to make any true difference, as one could reorder code to create another scenario where it breaks now (I think moving the array type front would likely do it).
Comment 16 Zorzella Mising name CLA 2007-06-08 13:18:18 EDT
This, btw, I believe is the same issue as was reported before on:

https://bugs.eclipse.org/bugs/show_bug.cgi?id=179902
Comment 17 Philipe Mulet CLA 2007-06-08 16:38:03 EDT
I don't think it is the same as bug 179902, which is a bound check issue. At least on the surface, it feels a different issue.
Comment 18 Carl Quinn CLA 2007-06-08 20:33:10 EDT
(In reply to comment #11)
Now, as soon as I have a patch, I would post it somewhere
> for you Carl to play with it. How does this sound ?

Sounds good, I can test any patches that you want me to.


Comment 19 Philipe Mulet CLA 2007-06-09 07:46:12 EDT
Carl - the more I investigate it and the more I believe a compiler should reject your code. The fact we accepted it in the past wouldn't change the resolution. Basically, the 2 parameters or 2 parameters+varargs param shouldn't make any difference. As said earlier, this is being confirmed currently. This to say that if a patch becomes available, it will likely still reject your code (unless I am proven to be wrong by the spec). 

As I explained in comment 12, it is possible to construct a testcase showing the inconsistence in javac. Now, currently, Eclipse may accept the code I think is invalid per mistake (which is what occurred in 3.2.2). The current state is still not perfect, as I think it is still possible to fool the compiler again (by ordering the parameters differently, and this is not good).

So there is still work to do on this bug, but the likely outcome is that once addressed the compiler will consistently reject such code patterns.

FYI - unsure how much familiar you are with capture conversion, but if not, then read this.

When you see code like this:

  public static <T> Comparator<T> compound(
      Comparator<? super T> a,
      Comparator<? super T> b) {
    return compound(asList(a, b));
  }

You'd think (intuitively) that the type of asList(a,b) is: List<Comparator<? super T>>. But it is not.

In fact, it is List<Comparator<? extends Object>> since both 'a' and 'b' arguments got captured, i.e. the type of 'a' in reference is instead:

Comparator<capture-1-of ? super T>
which is equivalent to introducing a new type variable on the fly: Comparator<U>

The type of 'b' is another capture (NOT the same)
which is equivalent to introducing yet a new type variable on the fly: Comparator<V>

So when inferring the type of asList(a,b), it is going to intersect the 2 captured types, producing (by the spec) List<Comparator<? extends Object>>

I agree this is highly counter-intuitive, but you can only blame the spec here. <g>
Comment 20 Philipe Mulet CLA 2007-07-02 11:25:55 EDT
FYI - started discussion with JLS master, this is retained in:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6573446

Javac behavior got confirmed to be invalid. Now, the idea is that a better behavior could be implemented, but the spec has first to evolve.

So for the current time being, the behavior you are seeing is the right one, as defined by the spec.

So technically, there is nothing to fix here.
(if you disagree, pls reopen)
Comment 21 Philipe Mulet CLA 2007-07-02 11:27:26 EDT
Added GenericTypeTest#test1142-1148
Comment 22 Frederic Fusier CLA 2007-12-20 06:58:49 EST
Verified for 3.4M1