improved return type-inference: request for review

Neal Gafter neal at gafter.com
Mon Oct 12 16:33:32 PDT 2009


OK, thanks!  I hope there is time to work on getting the spec done.

Cheers,
Neal

On Mon, Oct 12, 2009 at 1:10 AM, Maurizio Cimadamore <
Maurizio.Cimadamore at sun.com> wrote:

> Hi Neal,
> the changes proposed in my earlier email have never made their way into
> JDK7 (for several reasons). I think this is still the only proposal I've
> seen so far in order to solve the problem of type-variables escaping the
> context of type-inference. The only inference change that got in JDK 7 was
> the one for allowing constraint propagation from 15.12.2.7 to 15.12.2.8 (see
> 6650759) - all other javac inference changes can be regarded as bug fixes.
>
> Maurizio
>
> Neal Gafter wrote:
>
>> 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 <mailto:
>> 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 <mailto: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>
>>            <mailto: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/>
>>               <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/20091012/7b986f66/attachment.html 


More information about the compiler-dev mailing list