improved return type-inference: request for review

Neal Gafter neal at gafter.com
Thu Feb 26 09:39:37 PST 2009


Your points are well taken, but I don't know how to review the code in the
absence of a specification, as part of the purpose of the review is to
ensure that the code correctly implements the specification.

On Thu, Feb 26, 2009 at 9:32 AM, Maurizio Cimadamore <
Maurizio.Cimadamore at sun.com> wrote:

> Neal Gafter wrote:
>
>> Maurizio-
>>
>> Do you have a draft of the revised JLS specification that describes the
>> new behavior?
>>
> nope :-(
>
> actually it seems like this approach can be quite hard to specify correctly
> (at least this is what I've heard from Alex when I discussed this with him).
>
> Anyway I think that 15.12.2.8 should address several issues, not just this
> one: e.g.
>
> class Foo<X> {}
> <T extends Foo<T>> T m() { ... }
>
> <Z extends Foo<Z>> void test() {
>  Z test = m();
> }
>
> this generates the following constraints:
>
> Foo<Z>  >> T
> Foo<T>  >> T
>
> which in turns, means that:
>
> T = glb(Foo<T>, Foo<Z>) = ???
>
> I don't think that the above glb exists: both Foo<T> and Foo<Z> are class
> types, and neither is a subtype of the other.
> Should we reject this program? I don't think so, but it's not crystal clear
> from the JLS what should we do about this one.
> Both javac and Eclipse accepts it...
>
> Maurizio
>
>>
>> -Neal
>>
>>
>> On Tue, Feb 24, 2009 at 9:43 AM, Maurizio Cimadamore <
>> Maurizio.Cimadamore at sun.com <mailto:Maurizio.Cimadamore at sun.com>> wrote:
>>
>>    Hi,
>>    this is the fix [1] for an outstanding javac/JLS inference bug:
>>    [3] and
>>    [4] respectively. Those CRs both have to do with recursive bounds
>>    being
>>    included in the set of constraints generated by 15.12.2.8 (but not by
>>    javac). It's important to fix it both because it's preventing other
>>    important inference fixes (6638712) and because it might lead to
>>    problems when implementing an item of the project coin (namely
>>    'Concise
>>    initialization of generic variables' [2]).
>>
>>    Let's try to keep things simple: let's start from the example recently
>>    submitted by Martin, which I think it's the simpler test case I've
>>    seen
>>    so far:
>>
>>    class Test<X> {
>>
>>     <K extends Test<K>> void m() {}
>>
>>     void test() {
>>      m();
>>     }
>>    }
>>
>>    When you compile this, the compiler complains with the following
>>    message
>>    (afaik this affects all compilers, jdk 5, 6, 6-open, 7):
>>
>>    TestX.java:6: incompatible types; inferred type argument(s)
>>    java.lang.Object do not conform to bounds of type variable(s) K
>>    found   : <K>void
>>    required: void
>>      m();
>>       ^
>>    1 error
>>
>>    The message is somewhat messy, and hard to understand, but the meaning
>>    it's simple: the type inferred for K is Object; unfortunately
>>    Object is
>>    not compatible with K's declared bound; in fact Object should be a
>>    subtype of [K:=Object]Test<K> = Test<Object> which is obviously
>>    not true.
>>
>>    How happened that jaac inferred Object for K? According to JLS
>>    15.12.2.8, the following set of constraints should be derived for the
>>    above method call:
>>
>>    Test<K> >> K
>>
>>    K should then be inferred as glb(Test<K>) = Test<K>.
>>
>>    This is unfortunate at best for two reasons: first, the inferred type
>>    for K is defined in terms of K itself; secondly, Test<K> does not
>>    conform to K's declared bound, as Test<K> <: [K:=Test<K>]Test<K> =
>>    Test<Test<K>> does not hold.
>>
>>    However, javac impl does not seem to follow JLS here; in fact,
>>    javac is
>>    deliberately removing any recursive constraint from the set
>>    derived from
>>    15.12.2.8. Since there's just one constraint here, this leave us with
>>    the answer K = Object, which is slightly better than the solution
>>    provided by the JLS as it does not contains further references to K -
>>    but still does not satisfy K's declared bound.
>>
>>    [Sidebar: there other examples in which javac follows more closely the
>>    algorithm described by the JLS - in other words, sometimes javac fails
>>    to exclude recursive bounds from the set of generated constraints,
>>    because of minor trivial bugs - such bugs are however crucial when it
>>    comes to compile the code of some compiler regression tests!]
>>
>>    Now the though question: how should JLS deal with this? I think there
>>    are at least two available options:
>>
>>    1) Give up inference (which means that javac is right) and inferring
>>    Object for K (which then causes a compilation error because Object
>>    does
>>    not conform to K's bound).
>>
>>    2) Adopt a complex inference scheme that would allow 15.12.2.8 to
>>    yield
>>    an inferred type that (i) does not depend on K and (ii) respect K's
>>    declared (recursive) bound.
>>
>>    I went for (2), and here's why: in our running example (and in similar
>>    examples we have in our regression codebase), one could be tempted to
>>    say: ''this is just rubbish, fix your code, please''. On the other
>>    hand,
>>    last year I noticed that there are several use cases that would
>>    start to
>>    fail if we choosed (1):
>>
>>    Object value = Enum.valueOf((Class)Test.class, "");
>>
>>    where Enum.valueof is defined as follows
>>    <T extends Enum<T>> T valueOf(Class<T>, String)
>>
>>    In this case we have that T cannot be inferred from actual
>>    arguments (as
>>    the only argument is raw here). It follows that T must be inferred in
>>    15.12.2.8; here we have the following constraints:
>>
>>    T <: Object (for the return type)
>>    T <: Enum<T> (declared bound)
>>
>>    In other words we are in the same situation as above - only, this
>>    coding
>>    pattern is way more common (here inferring Object for T would
>>    cause the
>>    above code to be rejected). At this point you might have a
>>    question: how
>>    does javac deal with this code? We showed how javac is not capable of
>>    handling recursive bounds; the trick here is that javac (very
>>    surprisingly) never applies 15.12.2.8 (since this is an unchecked
>>    call).
>>    Which also means that javac never has to find an answer to the above
>>    question. Note that one day javac will be corrected so that 15.12.2.8
>>    will be applied even in this case - the fact that javac doesn't apply
>>    15.12.2.8 can be regarded as a bug (one of the biggest consequences of
>>    this is CR 6638712).
>>
>>    Back to the original question, given the only constraint (derived from
>>    15.12.2.8):
>>
>>    Test<K> >> K
>>
>>    What type should we infer for K?
>>
>>    I have shown that Object is not a viable answer (does not respect K's
>>    declared bound). At first it would seem that we should pick a type
>>    among
>>    the following candidates:
>>
>>    -Test<?>
>>    -Test<? extends Test<?>>
>>
>>    Those types are surely better than Object, but all have the problem of
>>    not satisfying K's bound: e.g.
>>
>>    Test<?> <: [K:=Test<?>]Test<K> = Test<Test<?>> (false because
>>    type-argument Test<?> does not contain ?)
>>
>>    I think there's only one solution to this problem, which is the
>>    following:
>>
>>    K inferred as #1, where #1 is a captured type variable whose upper
>>    bound
>>    is Test<#1>. Let's see how this works w.r.t. to bound checking:
>>
>>    #1 <; [K:=#1]Test<K> = Test<#1>
>>    ub(#1) = Test<#1> <: Test<#1> (true!)
>>
>>    This solution has all the required properties, in that (i) the
>>    inferred
>>    type does not contain reference to the type to be inferred (T) and
>>    (ii)
>>    the inferred type satisfy K's declared bound.
>>
>>    As you can see, the right answer is not that trivial - moreover as
>>    Alex
>>    mentioned in the related JLS CR [3], an acceptable solution should be
>>    able to deal with more complex cases like:
>>
>>    class Foo {
>>      <T extends List<U>, U extends List<T>> U foo() {return null;}
>>      List<?> s =  foo();
>>    }
>>
>>    Here we have two mutually referring recursive bounds (aaargh!). My
>>    solution works by inferring #1 (extends List<#2>) for T and #2
>>    (extends
>>    List<#1>) for U.
>>
>>    Final note:
>>    Since my approach makes use of some synthetically generated
>>    captured-type variables with recursive bounds, sometimes javac might
>>    crash because  of a problem in the toString routine for CapturedTypes
>>    (infinite loop). A recent, separate diagnostic work, ease this problem
>>    by truncating the infinite regression - what I'm saying is that,
>>    wthout
>>    proper diagnostic support, it won't be possible to support this fix as
>>    it is. The problem is that each captured type in the javac world is
>>    associated to an underlying wildcard type argument (the one that
>>    originated the captured type). In this case it's obvious that,
>>    given the
>>    fact that the captured type variable I'm introducing is synthetic,
>>    there's no such wildcard type argument - however I believe that
>>    displaying a meaningful wildcard to the user could help him understand
>>    what went wrong in case of errors - but one could always replace the
>>    synthetic captured type's wildcard argument with a simpler type,
>>    such as
>>    Test<?> (instead of the more precise Test<? extends #1> which leads to
>>    infinite recursion).
>>
>>    Maurizio
>>
>>    [1] http://cr.openjdk.java.net/~mcimadamore/6369605/<http://cr.openjdk.java.net/%7Emcimadamore/6369605/>
>>    <http://cr.openjdk.java.net/%7Emcimadamore/6369605/>
>>    [2] http://bugs.sun.com/view_bug.do?bug_id=4879776
>>    [3] http://bugs.sun.com/view_bug.do?bug_id=6369608
>>    [4] http://bugs.sun.com/view_bug.do?bug_id=6369605
>>
>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20090226/2092f264/attachment.html 


More information about the compiler-dev mailing list