Change default rules for declaration-site wildcards

Mostly this commit is about skipping wildcards that are redundant in some sense.
The motivation is that they looks `long` in Java code.

There are basically two important parts: return types and value parameters.

1. For return types default behaviour is skipping all declaration-site wildcards.
The intuition behind this rule is simple: return types are basically used in subtype position
(as an argument for another call), and here everything works well in case of 'out'-variance.
For example we have 'Out<Out<T>>>' as subtype both for 'Out<Out<T>>>' and 'Out<? extends Out<? extends T>>>',
so values of such type is more flexible in contrast to `Out<? extends Out<? extends T>>>` that could be used only
for the second case.

But we have choosen to treat `in`-variance in a different way: argument itself
should be rendered without wildcard while nested arguments are rendered by the rules
described further (see second part).

For example: 'In<Out<OpenClass>>' will have generic signature 'In<Out<? extends OpenClass>>'.
If we omit all wildcards here, then value of type 'In<Out<OpenClass>>'
will be impossible to use as argument for function expecting 'In<? super Out<? extends Derived>>'
where Derived <: OpenClass (you can check it manually :]).

And this exception should not be very inconvinient because in-variance is rather rare.

2. For value parameters we decided to skip wildcards if it doesn't make obtained signature weaker
in a sense of set of acceptable arguments.

More precisely:
    a. We write wildcard for 'Out<T>' iff T ``can have subtypes ignoring nullability''
    b. We write wildcard for 'In<T>' iff T is not equal to it's class upper bound (ignoring nullability again)

Definition of ``can have subtypes ignoring nullability'' is straightforward and you can see it in commit.

 #KT-9801 Fixed
 #KT-9890 Fixed
This commit is contained in:
Denis Zharkov
2015-11-23 19:45:48 +03:00
parent 1731cb8b40
commit 406e31f54a
29 changed files with 620 additions and 60 deletions
@@ -1,18 +1,15 @@
class Out<out T>
class X
// Why we want this to be translated to 'Out<Out<? extends X>> f()' instead of 'Out<Out<X>> f()'
// There are two instantiations of 'In' in this test: outer Out<...> and inner Out<X>
// So why do we want to put a wildcard on the inner one and not the outer?
// People don't want wildcards in return types, because they are _long_. So we try our best to remove wildcards where possible
// Not putting a wildcard on the outer occurrence is not imposing a restriction, actually it is removing one:
// anything that can be done with Out<? extends X> in Java can be done with Out<X>
// But omitting the wildcard on the inner occurrence is restrictive:
// one can add a List<String> to a List<List<? extends CharSequence>>,
// but not to a List<List<CharSequence>>,
// thus removing the wildcard would be restricting the use of the return value of the method, and we don't want do this.
// Why we want this to be translated to 'Out<Out<X>> f()' instead of 'Out<? extends Out<? extebds X>> f()'
// For return types default behaviour is skipping all declaration-site wildcards.
// The intuition behind this rule is simple: return types are basically used in subtype position
// (as an argument for another call), and here everything works well in case of 'out'-variance.
// For example we have 'Out<Out<T>>>' as subtype both for 'Out<Out<T>>>' and 'Out<? extends Out<? extends T>>>',
// so values of such type is more flexible in contrast to `Out<? extends Out<? extends T>>>` that could be used only
// for the second case.
fun f(): Out<Out<X>> = throw Exception()
// method: OutOfOutInOutPositionKt::f
// jvm signature: ()LOut;
// generic signature: ()LOut<LOut<+LX;>;>;
// generic signature: ()LOut<LOut<LX;>;>;