Support extension methods on a specialized version of a raw type
In addition to generic types, also support extension methods on specific parameterized instances of generic types. For example:
package extensions.java.util.List;
import manifold.ext.api.Extension;
import manifold.ext.api.This;
import java.util.List;
@Extension
public class MyListOfStringExt {
public static String foo(@This List<String> thiz) {
return thiz.get( 0 );
}
}
The idea is the type variables are inferred from the @This parameter type and, thus, only apply accordingly. An alternative format follows more closely with a normal extension method on a generic class, which specifies the type var. This one has the advantage of supporting covariance:
public static <E extends String> String foo(@This List<E> thiz) {
return thiz.get( 0 );
}
Should be straightforward to implement in Manifold. The challenge is probably more with Intellij to get code completion to filter out such extension methods when they don't apply.
Note many use-cases where generic method specialization is desired can be more concisely expressed using @Self, which is supported in extension methods. See https://github.com/manifold-systems/manifold/issues/47.
Hi @rsmckinney , Actually I have a use-case that I think cannot do without generic method specialization. Code shown below. Hope that helps to find a solution. Thanks!
package extensions.java.lang.Number;
import manifold.ext.api.Extension;
import manifold.ext.api.Self;
import manifold.ext.api.This;
import java.util.ArrayList;
import java.util.List;
@Extension
public class NumberExt {
// Non extension method, as base line
public static <T extends Number, LT extends List<? super T>> LT appendTo_nonExt(T thiz, LT list) {
list.add(thiz);
return list;
}
// Ideal solution IMO, but currently doesn't compile
// It is necessary to capture type of thiz using generic type T, thus it can be referred by other variable definitions.
// Use @Self on generic type T whose upper bound is the class to be extended, so that T is covariant to actual type.
// Or we can define a rule that type of thiz can be generic type, which must extend the class being extended,
// if so implicitly annotate @Self to that generic type, thus omitting explicit @Self.
public static <@Self T extends Number, LT extends List<? super T>> LT appendTo(@This T thiz, LT list) {
LT newList = (LT) new ArrayList();
list.add(thiz);
return list;
}
// Current solution. Without generic type, there's no way to relate return type to argument type
public static List<Number> appendTo_current(@This Number thiz, List<@Self Number> list) {
list.add(thiz);
return list;
}
public static void test() {
Integer i = 1;
List<Integer> li = new ArrayList<>();
List<Number> ln = new ArrayList<>();
// Check what non-ext method can do: return type is the same as type of list argument
List<Integer> li2 = appendTo_nonExt(i, li);
List<Number> ln2 = appendTo_nonExt(i, ln);
// extension method should be able to do the same as non-ext method
List<Integer> li3 = i.appendTo_current(li); // doesn't compile
List<Number> ln3 = i.appendTo_current(li); // compiles, but return type is inaccurate
// the same as non-ext method, if it works
List<Integer> li4 = i.appendTo(li);
List<Number> ln4 = i.appendTo(ln);
}
}
Hi @WilliamStone.
I think what you're after is this:
public static List<@Self Number> appendTo(@This Number thiz, List<@Self Number> list) {
list.add(thiz);
return list;
}
Hi @rsmckinney ,
I added your version of method as appendTo_self and modified test code to also test on a List<Object>, and it shows there is difference.
package extensions.java.lang.Number;
import manifold.ext.api.Extension;
import manifold.ext.api.Self;
import manifold.ext.api.This;
import java.util.ArrayList;
import java.util.List;
@Extension
public class NumberExt {
// Non extension method, as base line
public static <T extends Number, LT extends List<? super T>> LT appendTo_nonExt(T thiz, LT list) {
list.add(thiz);
return list;
}
// Ideal solution IMO, but currently doesn't compile
// It is necessary to capture type of thiz using generic type T, thus it can be referred by other variable definitions.
// Use @Self on generic type T whose upper bound is the class to be extended, so that T is covariant to actual type.
// Or we can define a rule that type of thiz can be generic type, which must extend the class being extended,
// if so implicitly annotate @Self to that generic type, thus omitting explicit @Self.
public static <@Self T extends Number, LT extends List<? super T>> LT appendTo_suggested(@This T thiz, LT list) {
LT newList = (LT) new ArrayList();
list.add(thiz);
return list;
}
// Current solution. Without generic type, there's no way to relate return type to argument type
public static List<Number> appendTo_current(@This Number thiz, List<@Self Number> list) {
list.add(thiz);
return list;
}
public static List<@Self Number> appendTo_self(@This Number thiz, List<@Self Number> list) {
list.add(thiz);
return list;
}
public static void test() {
Integer i = 1;
List<Integer> li = new ArrayList<>();
List<Number> ln = new ArrayList<>();
List<Object> lo = new ArrayList();
// Check what non-ext method can do: return type is the same as type of list argument
List<Integer> li2 = appendTo_nonExt(i, li);
List<Number> ln2 = appendTo_nonExt(i, ln);
List<Object> lo2 = appendTo_nonExt(i, lo); // >>>>>>>> OK; contra-variance, list of any super class of i's type is ok
// extension method should be able to do the same as non-ext method
List<Integer> li3 = i.appendTo_current(li); // doesn't compile, result type is wrong, must be List<Mumber>
List<Number> ln3 = i.appendTo_current(li); // compiles, but return type is inaccurate
List<Object> lo3 = i.appendTo_current(lo); // doesn't compile, argument type is wrong, must be List<Mumber>
// should be able to do the same as non-ext method, if it works
List<Integer> li4 = i.appendTo_suggested(li);
List<Number> ln4 = i.appendTo_suggested(ln);
List<Object> lo4 = i.appendTo_suggested(lo); // >>>>>>>>>> should be ok
// type parameter of list is restricted to be the same as thiz, no contra-variance
List<Integer> li5 = i.appendTo_self(li); // OK
List<Number> ln5 = i.appendTo_self(ln); // doesn't compile, can only receive list of integer
List<Object> lo5 = i.appendTo_self(lo); // doesn't compile, can only receive list of integer
}
}
Right... if you want contravariance on the return type, you'll have to use a non-extension method for that. This is a case that will be supported when this issue (#7) is implemented.
Glad to know that. Thanks!