Incoherent invocation type inference?

Remi Forax forax at univ-mlv.fr
Sun Jan 15 11:45:37 UTC 2017


Inference of 0-arity methods in Java is due to the fact that generics were introduced after the collection API in Java, and an explicit goal of the specification of generics was to be able to retrofit classes of java.util.  

An unsafe warning means that you may get a ClassCastException at runtime.

In the collection API, you have methods like Collections.emptyList() that has to return different types depending on the calling context.
Calling those methods should not emit a warning because you can not have a CCE at runtime, if you call a method that takes an E will have get an UnsupportedException, if you call a methods that returns an E you will get an IndexOutOfBoundsException or a NoSuchElementException. So you may get a lot of different exceptions but not a CCE, so a call to Collections.emptyList() is safe (with the current definition of what safe means) and the compiler should not trigger any warning.

C# is different because if you declare a static field instead of having one static value, you have one static value by type argument,
so for C#, it makes sense to force users to specify the type argument given that the returned value will be different.

The problem of the code of Bernard is not related to the inference, the problem is the unsafe cast in the implementation, if you use an unsafe cast, then you may have a ClassCastException at runtime. It's as simple as that.

regards,
Rémi

----- Mail original -----
> De: "B. Blaser" <bsrbnd at gmail.com>
> À: "Maurizio Cimadamore" <maurizio.cimadamore at oracle.com>
> Cc: compiler-dev at openjdk.java.net
> Envoyé: Dimanche 15 Janvier 2017 11:34:47
> Objet: Re: Incoherent invocation type inference?

> Hi,
> 
> 2017-01-14 14:12 GMT+01:00 B. Blaser <bsrbnd at gmail.com>:
>> Hi,
>>
>> 2017-01-14 1:42 GMT+01:00 Maurizio Cimadamore <maurizio.cimadamore at oracle.com>:
>>>
>>>
>>> On 13/01/17 21:58, B. Blaser wrote:
>>>>
>>>> Hi,
>>>>
>>>> 2017-01-13 15:12 GMT+01:00 Maurizio Cimadamore
>>>> <maurizio.cimadamore at oracle.com>:
>>>>>
>>>>> I think that's correct behavior. When you say something like:
>>>>>
>>>>> li = get(null);
>>>>
>>>> i = get(null); // li = ... is OK ;-)
>>>>
>>>>> T will get two constraints:
>>>>>
>>>>> * an upper bound (from declared bound) : T <: Iterable<String>
>>>>> * an upper bound (from return type compatibility) : T <: Integer
>>>>
>>>> Integer being final, a correct return type constraint would be <T =
>>>> Integer>, leading to incompatible bounds {T <: Iterable<String>, <T =
>>>> Integer>} and then javac should fail...?
>>>
>>> Well, if the spec would ever add some special treatment for finality in
>>> inference constraints (for instance cast conversion special cases final
>>> classes to give better results), that woul dbe a way yes - but I'll leave it
>>> to our spec experts.
>> In that case, I suggest something like below (line 899 in a more
>> recent version).
>>
>> http://hg.openjdk.java.net/jdk9/dev/langtools/file/b6960e2da008/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java#l899
>>
>>> In your examples though, what's really troublesome, is that you have a
>>> generic method whose type-arguments are only instantiated from the return
>>> type constraint. When you have a situation like that, and you mix unchecked
>>> cast, things are going to blow up almost always - final or not.
> 
> I don't agree with that. Let "Iterable -> List -> ArrayList" be an
> unique hierarchy. In our example, "li = get()" is meaningful and won't
> ever fail, but "i = get()" will always blow up!
> 
>>> I'd be more
>>> supportive for an optional Lint warning for cases where inference doesn't
>>> get any constraints from the actual arguments
>> As unchecked cast produces a warning, javac should probably also emit
>> at least a warning in our situation; but I'm not sure this would be
>> really satisfying?
> 
> Could we express a rule for a "lint" warning (this is not
> straightforward, I think)? Speaking of rules and inference, I was
> wondering if it could be imaginable to express facts about known types
> and bounds with formal logic rules; and then infer type variables
> using built-in systems like Prolog?
> 
>> diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
>> b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
>> --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
>> +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
>> @@ -966,9 +966,11 @@
>>                      //if 's' is 'null' there's no instantiated type U for which
>>                      //U <: s (but 'null' itself, which is not a valid type)
>>                      return false;
>> +                } else if ((s.tsym.flags() & Flags.FINAL) != 0) {
>> +                    t.addBound(InferenceBound.EQ, s, Types.this);
>> +                } else {
>> +                    t.addBound(InferenceBound.UPPER, s, Types.this);
>>                  }
>> -
>> -                t.addBound(InferenceBound.UPPER, s, Types.this);
>>                  return true;
>>              }
>>
>>
>>> - just for a quick comparison,
>>> C# treats these cases as hard errors; that's not the way Java inference has
>>> been designed from the start, but that doesn't mean that we couldn't help
>>> programmers steer away from dubious  generic methods (like your 0-ary get()
>>> method below).
> 
> I think so, too...
> 
>>> Maurizio
> 
> Bernard
> 
>>>>> The language will then infer T = Integer & Iterable<String>, an
>>>>> intersection
>>>>> type.
>>>>>
>>>>> Normally this would be fine - but the issue here is that Integer is a
>>>>> final
>>>>> class, so that intersection type has no witnesses - e.g. there's no way
>>>>> for
>>>>> get() to construct a type that is both an Integer and an Iterable<String>
>>>>> -
>>>>> so that will almost always fail - unless get() returns null.
>>>>>
>>>>> That said, I'm not aware of any check in the spec for preventing an
>>>>> intersection type to mention a final class as one of its components so,
>>>>> while this does look weird, I think the compiler is doing what the
>>>>> language
>>>>> spec says.
>>>>>
>>>>> Maurizio
>>>>>
>>>>>
>>>>>
>>>>> On 13/01/17 13:01, B. Blaser wrote:
>>>>>>
>>>>>> mport java.util.*;
>>>>>>
>>>>>> public class Issue  {
>>>>>>       <T extends Iterable<String>> T get() { return (T)new
>>>>>> ArrayList<String>(); }
>>>>>>       <T extends Iterable<String>> T get(T t) { return (T)new
>>>>>> ArrayList<String>(); }
>>>>>>
>>>>>>       void run() {
>>>>>>           List<String> li = null;
>>>>>>           LinkedList<String> ll = null;
>>>>>>           Integer i = null;
>>>>>>
>>>>>>           li = get(null);
>>>>>>           i = get(null); // Shouldn't compile? and will fail at runtime.
>>>>>> //        i = get(li); // Fails as expected
>>>>>>
>>>>>>           li = get();
>>>>>>           ll = get(); // OK, but will fail at runtime due to unchecked
>>>>>> conversion in get().
>>>>>>           i = get(); // Shouldn't compile? and will fail at runtime.
>>>>>>       }
>>>>>>
>>>>>>       public static void main(String... args) {
>>>>>>           new Issue().run();
>>>>>>       }
>>>>>> }
>>>>>
>>>>>


More information about the compiler-dev mailing list