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