5

Given the following two class definitions:

class C1<T extends C1<T>> {}

class C2<U> extends C1<C2<U>> {}

And the following type declaration:

C1<C2<?>> a;

Intuitively it feels the declared type a should be valid, but this is not the way JDK-8u45 behaves. Instead we get something like the following output:

Test.java:3: error: type argument C2<?> is not within bounds of type-variable T
        C1<C2<?>> a;
             ^
  where T is a type-variable:
    T extends C1<T> declared in class C1
1 error

(Edit: I was being a dingus here, this part has been answered: C2<?> does not extend C1<C2<?>>. The issue regarding the declaration of c below is still an open question, though.)

But C2<?> does extend C1<C2<?>>, which would appear to trivially satisfy the bound. Examination of the JLS provides no further illumination so far as I can see. It really should just be as simple as satisfying the bound by the subtype relation, since C2<?> is not a wildcard type and therefore capture conversion is just an identity conversion on the argument.

There are situations where it becomes a little less clear, for example, take the following class definitions:

class C3<T extends C3<?>> {}

class C4<Y, Z> extends C3<C4<Z, Y>> {}

class C5<X extends C3<X>> {
    void accept(X x);
}

All of this is fine, but then if we try the following declaration:

C5<C6<?, ?>> b;

Things become stranger. C6<?, ?> is a subtype of C3<C6<?, ?>>, so the declaration should be valid according to my interpretation of the specification as given above regarding the declaration C1<C2<?>>. The problem is that clearly not every possible subtype of C6<?, ?> actually satisfies that bound, so now for example C5.accept() resolves its parameter type to C6<?, ?> and so can accept arguments which violate the bounding on X, i.e. any where the parameterizations of Y and Z are not identical.

Where am I going wrong here? Is my understanding of the subtype relationship insufficient?

(Edit: The following part of the question is still unanswered, but I've moved it to a new question here since it's a completely different issue really... Sorry for making a mess and not using the site very well haha...)

Aside from this, I'm also having some problems with capture conversion in similar situations. Take the following type declaration:

C1<? extends C2<?>> c;

Unlike the similar declaration a at the start, this compiles fine in JDK-8u45. If we examine the specification for capture conversion, though, it appears this declaration should result in a compile time error this time.

In particular, the upper bound of the new type variable capture CAP#T is given by glb(Bi, Ui[A1:=S1,...,An:=Sn]), where in this case Bi resolves to the wildcard bound C2<?> and Ui[A1:=S1,...,An:=Sn] resolves to C1<CAP#T>.

From this, glb(C2<?>, C1<CAP#T>) resolves to the intersection type C2<?> & C1<CAP#T>, which is invalid, because C2<?> and C1<CAP#T> are both class types, not interface types, but neither one of them is a subtype of the other.

This (apparent) rule violation is made more clear in the definition of the intersection type itself.

I'm sure it's not a bug and I'm just making some simple mistakes somewhere... but if nobody here can shed any light on this for me I'll try the compiler-dev mailing list or something.

Thanks for any help!

Community
  • 1
  • 1
Elias Vasylenko
  • 1,524
  • 11
  • 21
  • It's a fascinating issue. I'm just curious, is this some theoretical problem you have or do you have some real world classes for this? I just can't imagine where one would use `class C1> {}` or `class C2 extends C1> {}`. – Kris Jun 11 '15 at 12:04
  • Valid question! I have no specific real world application of this particular issue... But I *do* need to understand the proper expected behaviour, as I'm writing a bunch of reflective utilities over the type system and want the behaviour to match the spec/JDK. Currently it's mostly working, including type inference of generic method invocation, but there are a couple of edge cases (like this one) which cause issues. If you want to take a look it's hosted here: https://github.com/StrangeSkies/uk.co.strangeskies. No wiki yet, but the javadocs are all present for the `.reflection` package. – Elias Vasylenko Jun 11 '15 at 12:18
  • This actually has very little to do with capture conversion. Instead, you should look in 4.10.2 and 4.5.1 for answers. You're being misled as long as you view this as related to capture conversion. For example, your reasoning for why `C1 extends C2>>` shouldn't compile is nonsense. (I think it's also wrong, I don't think there is an intersection type if capture conversion *were* applied, because [capture conversion would not be applied recursively](http://stackoverflow.com/a/30385343/2891664) but I just woke up so I'm a bit groggy.) – Radiodef Jun 11 '15 at 12:41
  • I know the first part has nothing to do with capture conversion, but the declaration of `c` does. And I didn't apply it recursively, I only applied it to `C` to give `C` as described in sec. 4.5. Because the type argument is a wildcard with an upper bound, and the type parameter itself also has an upper bound, the resulting upper bound is the intersection type of those two bounds: "If `Ti` is a wildcard type argument of the form `? extends Bi`, then `Si` is a fresh type variable whose upper bound is `glb(Bi, Ui[A1:=S1,...,An:=Sn])` and whose lower bound is the null type." – Elias Vasylenko Jun 11 '15 at 13:02
  • I also have looked through 4.10.2 and 4.5.1 before a number of times, but I cannot see where they contradict the analysis I gave in my question regarding the declarations `a` and `b`. If you could point out where they do when you're less groggy, I would be very grateful, thanks! – Elias Vasylenko Jun 11 '15 at 14:04
  • @Radiodef thanks again for the help. bayou.io has clarified in an answer what my silly mistake was wrt the first part of the question, and I've marked that answer as correct and separated the capture conversion issue into another question here: http://stackoverflow.com/questions/30788366/capture-conversion-issue-in-java-wrt-reconciliation-of-jls-and-actual-jdk-behav It should be more clear there that recursive capture conversion is not the issue, as I have swapped out the inner wildcard with a class type, and the question essentially remains identical. – Elias Vasylenko Jun 11 '15 at 18:23
  • 1
    Interesting - IntelliJ 14.1.3 does not show an error for this, while `javac` does. Created an [IntelliJ bug report](https://youtrack.jetbrains.com/issue/IDEA-141388). – Jesper Jun 11 '15 at 19:51

1 Answers1

3

While
C2<x> extends C1<C2<x>> for any reference type x,
it is not the case that
C2<?> extends C1<C2<?>>

A wildcard ? is not a type. It is a type argument. The syntax though is very deceiving (by design).

Let's use a different syntax - if there's any 1st-level wildcard, use {} instead of <>, e.g.

List{?},  Map{String, ? extends Number}

The meaning of {?} is to declare a union type

List{? extends Number}  ==  union of List<Number>, List<Integer>, List<Long>, ....

It's easy to see that, List<Integer> is a subtype of List{? extends Number}; and
List{? extends Number} is a subtype of List{? extends Object}

However, there's no way that Foo{?} is a subtype of a Foo<x>.


In our syntax, <> is reserved for substituting type vars with types. So we write
List<String>, C2<Integer>, etc. It's easy to understand their meaning - just replace T's with String in the source code of List, we get a good-old plain class.

    interface List<String>
        String get(int)

This cannot be done for wildcard - it makes no sense

    interface List<?>
        ? get(int)

So it is not allowed to new ArrayList{?}(), or class MyList implements List{?}

So, how can we use List{?}? What methods we can call on it?

When the type of an expression is a List{?}, we know that it is an object, and the object must belong to a subclass of List<x> for some unknown type x. This is wildcard capture

obj is a List{?}  =>  obj is a List<x>, where x a subtype of Object.

Even though the exact type of x is unknown at compile time, we can still do the substitution

    interface List<x>
        x get(int)

so we can make sense of the call obj.get(0); it returns x, and x is a subtype of Object; so we can assign the return value to an Object.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Ah yes, right you are, that was a bit of a brain fart on my part. Thanks very much! Any thoughts on the `C1 extends C2>> c;` issue with intersection type on the upper bound? I think that might be a little more tricky, but maybe not... – Elias Vasylenko Jun 11 '15 at 17:15
  • I've put the second part of the question into a new question so I can just mark your answer as correct and get rid of all the cruft, since the second part is basically a completely separate issue. The new question is here: http://stackoverflow.com/questions/30788366/capture-conversion-issue-in-java-wrt-reconciliation-of-jls-and-actual-jdk-behav – Elias Vasylenko Jun 11 '15 at 18:12