eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
395 stars 62 forks source link

Troublesome cast from Java object to Java HashMap and ceylonification via CeylonMap #7442

Open xkr47 opened 5 years ago

xkr47 commented 5 years ago

I wanted to check if a Java object is a Java HashMap and iff then pass it to CeylonMap, so with some guidance I was trying to use:

import java.util { JHashMap = HashMap }

Object obj = ...;

if (is JHashMap<out Object, out Anything> obj) {
    // above line gives an error:
    // error: type condition cannot be fully checked at runtime: 'JHashMap<Anything,Anything>'
    // involves a type argument that is unchecked for Java class instances
    value map = CeylonMap(obj);
    ...

A workaround is to use e.g.

if (is JHashMap<out Anything, out Anything> obj) {
    assert (is JHashMap<out Object, out Anything> obj);
    value map = CeylonMap(obj);
    ...

.. which only gives a warning for the assert line.

Based on discussions with @gavinking we were considering that "perhaps ... there should be an implied upper bound of Object on all Java type args"..

.. but the question is perhaps also what to do with the fact that Java HashMaps can have null keys while the CeylonMap adapter just assumes there are no null keys in the given maps and declare that the Key type parameter does not intersect with Null. Changing CeylonMap to accept Java HashMaps with null keys but to satisfy the Map interface with non-null keys and do the filtering internally would perhaps be an option?

jvasileff commented 5 years ago

I don't think this is a compelling example for the change. The narrowing issue is largely orthogonal to null safety introp. You might also want to narrow to JHashMap<String, ...>, which obviously can't be helped by an upper bound.

Types like JMap<String, String?> and JFuture<T?> should also be considered. Interop should not force you to abandon null safety when using Java types, and you should be able to fully use Java types in pure Ceylon code without compromise (aside from is tests involving TPs).

Finally, IIRC there already is an upper bound on TPs of methods, and long ago @renatoathaydes pointed out some real usability problems with it (but I don't recall the details.)

jvasileff commented 5 years ago

Java type parameters can't have default type arguments, so perhaps the syntax JavaType<> should be used for Java raw types.

That is, have implied default arguments of out Anything for Java type parameters. The example would then be:

if (is JHashMap<> obj) {
    assert (is JHashMap<out Object, out Anything> obj);
    value map = CeylonMap(obj);
    ...
renatoathaydes commented 5 years ago

Hey guys, good to see you're still making improvements... @jvasileff I can't remember the compatibility issues anymore, but honestly, in this case I don't fault Ceylon at all... if you have an Object that you think is a Map<T, V>, I would say the only way you should be able to get that type is by actually copying the raw JHashMap<Anything,Anything> (after doing an assert or if-is) into a new, well-typed Ceylon Map, asserting each key-value type as you go. As far as I can see, it's the same thing in Java... or other JVM languages... anything else is not type-safe.

jvasileff commented 5 years ago

Here’s the old issue: https://gitter.im/ceylon/user?at=567076228b28de8704521446

I suspect, given enough time thinking through this, the conclusion would be to remove the constraint on method TPS rather than adding one to type TPs. Take note of @renatoathaydes comment “but I need to return Null as well“ and that we also want to have flexibility when refining Java types.