Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

idea: check and fix use of declaration-site type variance #2621

Closed
vlsi opened this issue Jan 10, 2023 · 27 comments
Closed

idea: check and fix use of declaration-site type variance #2621

vlsi opened this issue Jan 10, 2023 · 27 comments
Assignees
Labels
recipe Requested Recipe

Comments

@vlsi
Copy link

vlsi commented Jan 10, 2023

See:

Currently, Java requires use-site type variance, so if someone has Function<IN, OUT> method parameter, it should rather be Function<? 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:

@jkschneider
Copy link
Member

I'm a big fan of this for a set of hard-coded well-known classes.

@jkschneider
Copy link
Member

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?

@vlsi
Copy link
Author

vlsi commented Jan 10, 2023

I think so. However, I suggest discussing meta-issues in jspecify issue (the first link) to avoid splitting the questions/findings into several issues ;)

@jkschneider
Copy link
Member

Automatically added variance should skip type parameters that are final classes as well.

@jkschneider
Copy link
Member

jkschneider commented Jan 11, 2023

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) {
    }
}

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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

@jkschneider
Copy link
Member

You can tinker with the current form of this recipe at https://public.moderne.io/recipes/org.openrewrite.java.cleanup.CommonDeclarationSiteTypeVariances.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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

@jkschneider
Copy link
Member

In the latest commit I added an option excludeFinalClasses so you can control whether variance is added to final classes.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

What I mean is ? super variance should be added for google/error-prone#3711 (comment) google/error-prone#3711 (comment) all the classes no matter if they are final or not.

? super Object is special: google/error-prone#3711 (comment)

@jkschneider
Copy link
Member

By setting excludeFinalClasses to false (which is the default), ? super variance will be added for all the classes no matter if they are final.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

What is the point of skipping ? super variance if somebody sets excludeFinalClasses=true?

@jkschneider
Copy link
Member

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.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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

@jkschneider
Copy link
Member

jkschneider commented Jan 11, 2023

I see, so you were literally meaning just the contravariant case? Makes sense. Should we avoid ? extends on final classes?

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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.

jkschneider added a commit that referenced this issue Jan 11, 2023
@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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

@jkschneider
Copy link
Member

An unfortunate case is List

And similar for every collection type likely...

@jkschneider
Copy link
Member

Honestly these results are looking pretty good https://public.moderne.io/results/aX1Tl. Once b47d9b3 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).

@jkschneider
Copy link
Member

One more thing: this recipe shouldn't modify any parameter that is assigned to a field in the method body.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

Why? On contrary, it should modify the type of the field as well.

@jkschneider
Copy link
Member

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.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

Iterable<OUT>
Iterator<OUT>
Stream<OUT>

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

Can the recipie be suppressed for a particular placeholder?

E.g. Consumer<@SuppressWarnings("wildcards") String>

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

-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>.

@vlsi
Copy link
Author

vlsi commented Jan 11, 2023

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>.

@jkschneider jkschneider removed this from the 7.35.0 milestone Jan 22, 2023
@kunli2 kunli2 self-assigned this Feb 27, 2023
@kunli2
Copy link
Contributor

kunli2 commented Mar 1, 2023

I found there is already a recipe there: DeclarationSiteTypeVariance. so closing this out.

@kunli2 kunli2 closed this as completed Mar 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
recipe Requested Recipe
Projects
Archived in project
Development

No branches or pull requests

4 participants