rewrite-static-analysis icon indicating copy to clipboard operation
rewrite-static-analysis copied to clipboard

`UnnecessaryExplicitTypeArguments` incorrectly removes type parameter needed for dependent type parameters.

Open motlin opened this issue 2 months ago • 1 comments

The UnnecessaryExplicitTypeArguments recipe incorrectly removed an explicit type parameter in a location where it was necessary (specifically, a method with type parameters where one type parameter extends another).

  • This is similar but distinct from the previously fixed issue #164 "Unnecessary explicit type arguments removes necessary type arguments." Here type arguments needed for methods with dependent type parameters (e.g., <T, S extends T>).

Minimal Reproduction

import java.util.Comparator;
import java.util.List;

public class MinimalReproOpenRewriteBug {

    /**
     * A method with stricter type bounds than standard Java APIs.
     * The type parameter S extends T, which requires explicit type specification
     * when T cannot be inferred from the Comparator argument alone.
     */
    public static <T, S extends T> Comparator<Iterable<S>> lexicographical(Comparator<T> comparator) {
        return (list1, list2) -> {
            var it1 = list1.iterator();
            var it2 = list2.iterator();
            while (it1.hasNext() && it2.hasNext()) {
                int cmp = comparator.compare(it1.next(), it2.next());
                if (cmp != 0) {
                    return cmp;
                }
            }
            return Boolean.compare(it1.hasNext(), it2.hasNext());
        };
    }

    // This works - explicit type parameter
    private static final Comparator<Iterable<Integer>> WORKS =
        lexicographical(Comparator.<Integer>naturalOrder());

    // This fails to compile - type parameter removed by OpenRewrite
    private static final Comparator<Iterable<Integer>> FAILS =
        lexicographical(Comparator.naturalOrder());

    record Example(List<Integer> numbers) implements Comparable<Example> {
        private static final Comparator<Example> COMPARATOR =
            Comparator.comparing(Example::numbers, WORKS);

        @Override
        public int compareTo(Example other) {
            return COMPARATOR.compare(this, other);
        }
    }
}

OpenRewrite should preserve the explicit type parameters here. What actually happened it changed:

Comparator.<Integer>naturalOrder()

to:

Comparator.naturalOrder()

This causes the compilation error:

MinimalReproOpenRewriteBug.java:50: error: method lexicographical in class MinimalReproOpenRewriteBug cannot be applied to given types;
        lexicographical(Comparator.naturalOrder());
        ^
  - required: Comparator<T#1>
  - found:    Comparator<T#2>
  - reason: inference variable S has incompatible upper bounds T#3,Comparable<? super T#3>,Object,T#1
  - where T#1,S,T#2,T#3 are type-variables:
    - T#1 extends Object declared in method <T#1,S>lexicographical(Comparator<T#1>)
    - S extends T#1 declared in method <T#1,S>lexicographical(Comparator<T#1>)
    - T#2 extends Comparable<? super T#2>
    - T#3 extends Comparable<? super T#3> declared in method <T#3>naturalOrder()
1 error

motlin avatar Nov 14 '25 20:11 motlin

Thanks for the report @motlin ! Helpful to see your case of <T, S extends T> Comparator<Iterable<S>>. Now we need to figure out how to recognize that case and avoid removing the explicit type arguments when used as an argument to such a method.

timtebeek avatar Nov 15 '25 12:11 timtebeek