Closed vlsi closed 1 year ago
I'm a big fan of this for a set of hard-coded well-known classes.
Are there also a series of types for which we wouldn't add variance? For example: Function<String, Car>
should be Function<String, ? extends Car>
not Function<? super String, ? extends Car>
right?
I think so. However, I suggest discussing meta-issues in jspecify issue (the first link) to avoid splitting the questions/findings into several issues ;)
Automatically added variance should skip type parameters that are final classes as well.
Note that when a method overrides another method, it cannot use declaration-site type variance without changing the overridden method. Nor can on overridding method remove declaration-site type variance when overriding one that has it.
interface Test {
void test(Function<List, List> f);
}
class TestImpl implements Test {
@Override
public void test(Function<? super List, ? extends List> f) {
}
}
A class can become non-final, so having variance for final classes might be helpful.
However, it is unlikely String can become open, so ? extends String
is probably useless
You can tinker with the current form of this recipe at https://public.moderne.io/recipes/org.openrewrite.java.cleanup.CommonDeclarationSiteTypeVariances.
I think Consumer<? super String>
should be suggested even though String is final, so rewrite should recommend wildcards even for final classes in IN positions
In the latest commit I added an option excludeFinalClasses
so you can control whether variance is added to final classes.
What I mean is ? super variance should be added for https://github.com/google/error-prone/issues/3711#issuecomment-1377732638 https://github.com/google/error-prone/issues/3711#issuecomment-1377732638 all the classes no matter if they are final or not.
? super Object is special: https://github.com/google/error-prone/issues/3711#issuecomment-1377732638
By setting excludeFinalClasses
to false
(which is the default), ? super
variance will be added for all the classes no matter if they are final.
What is the point of skipping ? super variance if somebody sets excludeFinalClasses=true?
I'm just coming around on this -- my intuition was originally that ? super Integer
just looked overly pedantic? And that maybe there wouldn't be any harm in going part way. But maybe I'm just wrong altogether.
Not really. Consider
void act(Consumer<Integer> intConsumer) { }
...
Consumer<Object> tst=...;
act(tst); <-- this will fail to compile.
// act(Consumer<? super Integer> ...) will heal the case
I see, so you were literally meaning just the contravariant case? Makes sense. Should we avoid ? extends
on final classes?
I am not sure regarding ? extends
for final classes.
For instance, Supplier<? extends String>
would look weird, so it might make sense to use simpler signatures like Supplier<String>
, especially for well-known final classes that will never become open.
I guess it might be great to have an option like suppressOutVarianceForClasses:..., and put String, Integer, there by default. Disabling out variance for non-jdk classes sounds too fragile.
An unfortunate case is List
which should be List<IN>
for read-only case, and List<INVARIANT>
for read-write (Kotlin's MutableList).
Automatic fix sounds complicated for that case
An unfortunate case is List
And similar for every collection type likely...
Honestly these results are looking pretty good https://public.moderne.io/results/aX1Tl. Once b47d9b34cb5b4742ebc73f0028569f90c61e0df7 finishes building we'll have the ability to suppress out variance for final classes (as well as in and out variance for a specified set of excluded glob patterns of fully qualified types).
One more thing: this recipe shouldn't modify any parameter that is assigned to a field in the method body.
Why? On contrary, it should modify the type of the field as well.
For completeness yes. Trying to limit complexity somewhat. The assignment may be to a base class coming from a binary dependency, but easy enough to check for.
Iterable<OUT>
Iterator<OUT>
Stream<OUT>
Can the recipie be suppressed for a particular placeholder?
E.g. Consumer<@SuppressWarnings("wildcards") String>
-MicrometerCollector collector, HistogramSupport histogramSupport, Supplier<Exemplar[]> exemplarsSupplier,
+MicrometerCollector collector, HistogramSupport histogramSupport, Supplier<? extends Exemplar[]> exemplarsSupplier,
It looks like a false-positive. One can't inherit arrays, so ? extends NonNullableArrayType
should definitely be suppressed.
However, Supplier<? extends Exemplar @Nullable []>
should indeed be converted to Supplier<? extends Exemplar @Nullable []>
. In other words, if we accept Supplier
that produces Exemplar[]
or a null
reference, then we should accept even such suppliers that produce non-null Exemplar[]
.
Just in case, Supplier<@Nullable Exemplar[]>
should be kept intact as @Nullable
in that position is related to the element type.
The same goes for String
: Supplier<@Nullable String>
should be converted to Supplier<? extends @Nullable String>
.
One more idea: I guess the recepie should work both ways: if it detects Supplier<? extends String>
, then it should suggest simplifying the type to Supplier<String>
.
I found there is already a recipe there: DeclarationSiteTypeVariance
. so closing this out.
See:
Currently, Java requires use-site type variance, so if someone has
Function<IN, OUT>
method parameter, it should rather beFunction<? super IN, ? extends OUT>
.Unfortunately, it is not easy to notice that
? super
and? extends
is missing, so it would be nice if there was a tool that could detect missing variance and suggest adding it.The list of well-known classes could be hard-coded within OpenRewrite:
Function
,Predicate
,BiFunction
,Consumer
,Supplier
, and so on.Here is a recent case:
WDYT?
Corner cases:
See also: