improved return type-inference: request for review

Neal Gafter neal at gafter.com
Sun Oct 11 11:00:00 PDT 2009


Now that we're rapidly approaching the feature-complete date for JDK7, what
are the plans to distribute a proposed specification for these (already
checked-in) changes?  Or, alternately, are there plans to back out the
changes?

(Or have we decided that we no longer need to have the implementation
following some specification?)

Cheers,
Neal

On Thu, Feb 26, 2009 at 10:39 AM, Neal Gafter <neal at gafter.com> wrote:

> 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/20091011/b47480c0/attachment.html 


More information about the compiler-dev mailing list