inference trouble with recursive generics and raw types

Dan Smith daniel.smith at oracle.com
Mon Nov 4 08:38:55 PST 2013


On Nov 3, 2013, at 10:27 AM, Stephan Herrmann <stephan.herrmann at berlin.de> wrote:

> Working on the latest spec I'm seeing a discrepancy between
> the spec and actual javac behavior.
> 
> interface Foo<T extends Foo<T>> {
> }
> 
> class Bar<Q> {
> }
> 
> public class X {
> void readDatabase() {
> Bar<Foo> bar = new Bar<Foo>();
> read(bar, "sadasd"); // infer this call!
> }
> 
> <P extends Foo<P>, D extends Bar<P>>
> D read(D d, String s) {
> return d;
> }
> }

This is essentially the same issue that you reported on October 10, right?

public class X {
 public static void main(String[] args) {
   EntityKey entityKey = null;
   new EntityCondenser().condense(entityKey);
 }
 public static class EntityCondenser {
   <I, E extends EntityType<I, E, K>, K extends EntityKey<I>> void condense(K entityKey) {
   }
 }
 public class EntityKey<I> {}
 public interface EntityType<
   I,
   E extends EntityType<I, E, K>,
   K extends EntityKey<I>> {
 }
}

> I see type inferencing regarding the invocation of read(..)
> producing the following:
> (a) declaration of type variable P produces this dependency:
> P#0 <: Foo<P#0>
> (where P#0 is an inference variable representing P)
> (b) declaration of type variable D produces this type bound:
> D#1 <: Bar<P#0>
> (c) the constraint formula for the argument expression 'bar' yields:
> D#1 :> Bar<Foo>
> (d) incorporation of (b) and (c) yields this constraint:
> ⟨Bar<Foo> <: Bar<P#0>⟩
> (e) reduction on (d) yields this type bound:
> P#0 :> Foo
> (f) incorporating (a) with (e) yields this type constraint:
> ⟨Foo <: Foo<P#0>⟩
> At this point inference fails because the raw type Foo is not
> recognized as a legal subtype of Foo<P#0>: In 18.2.3 the
> 5.2nd bullet applies: We need to find the parameterization
> of Foo that is a supertype of Foo, which, however, cannot be
> determined since Foo (substitute for S) is a raw type.
> 
> Is there a mistake in my derivation?

Nope, I agree with your explanation (except step (e) should be an eq bound, not a lower bound).  Crucially, raw Foo is not within the upper bound of P#0.

> OTOH, all compilers that I tested accept the above program
> (from javac 1.5 to 8b112 as well as ecj).

Yes, I identified this as a bug in your last report.

Significantly, an invocation of the form 'this.<Foo,Bar<Foo>>read(bar, "sadasd")' will compile.  So this isn't really specifically an inference issue: the compilers simply believe that a raw type is "within" a bound that is a parameterization of that type.  And that interpretation is flat-out wrong.  Bounds are subtyping assertions.  JLS is consistent about this -- see 4.5 and 15.12.2.2-4.

Here's my javac bug report: https://bugs.openjdk.java.net/browse/JDK-8026527

> In spite of all talk about reducing the support for raw types, the
> above example seems to be a relevant pattern, were avoidance of
> raw types is not as easy as just filling in some type parameters
> (or is the above program actually unsafe and thus can only be
> written using raw types?)
> 
> Unless I'm missing s.t.:
> 
> Either the spec needs to be extended to gracefully handle raw
> types in at least this particular rule.
> 
> Or all compilers have a bug and must be fixed :)

The compilers have a bug.

It might make sense to reexamine the spec here if there were some sort of lack of clarity, but there's really not.  And making a change in the interpretation of bounds would be a big deal, because the use of subtyping here is fundamental to a lot of things (like inference, type variable subtyping, etc.) that are built on top.

—Dan


More information about the lambda-spec-observers mailing list