[
https://issues.apache.org/jira/browse/GROOVY-8788?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17580951#comment-17580951
]
Eric Milles commented on GROOVY-8788:
-------------------------------------
I can fix this by updating {{StaticTypeCheckingSupport#chooseBestMethod}} (see
below). There are a number of cases that stretch intuition and may result in
breaking existing code. In both cases the method on the left is chosen by the
static compiler and the method on the right is chosen by the dynamic runtime
and the new static compiler (with chooseBestMethod below).
The dynamic runtime has preference for parameter matching and the static
compiler prefers receiver matching. This is the source of the problem
described in the original post. Changing this preference will bring closer
alignment for the two modes but has some consequences.
{code:groovy}
@groovy.transform.CompileStatic void test(Set one, Set two) {
one.intersect(two) // Set#intersect(Iterable) vs.
Collection#intersect(Collection)
}
@groovy.transform.CompileStatic void test(Map<String,Number> map, String key) {
Number num = map[key] // Map#getAt(Object) vs. Object#getAt(String)
}
{code}
[~paulk] [~blackdrag]
{code:java}
/**
* Returns the method(s) which best fit the argument types.
*
* @return zero or more results
*/
public static List<MethodNode> chooseBestMethod(final ClassNode receiver,
final Collection<MethodNode> methods, final ClassNode... argumentTypes) {
if (!asBoolean(methods)) {
return Collections.emptyList();
}
int bestDist = Integer.MAX_VALUE;
List<MethodNode> bestChoices = new LinkedList<>();
boolean noCulling = methods.size() <= 1 ||
"<init>".equals(methods.iterator().next().getName());
Iterable<MethodNode> candidates = noCulling ? methods :
removeCovariantsAndInterfaceEquivalents(methods);
for (MethodNode candidate : candidates) {
MethodNode safeNode = candidate;
ClassNode[] safeArgs = argumentTypes;
boolean isExtensionMethod = candidate instanceof
ExtensionMethodNode;
if (isExtensionMethod) {
int nArgs = argumentTypes.length;
safeArgs = new ClassNode[nArgs + 1];
System.arraycopy(argumentTypes, 0, safeArgs, 1, nArgs);
safeArgs[0] = receiver; // prepend self-type as first argument
safeNode = ((ExtensionMethodNode)
candidate).getExtensionMethodNode();
}
/* TODO: corner case
class B extends A {}
Animal foo(A a) {}
Person foo(B b) {}
B b = new B()
Person p = foo(b)
*/
ClassNode declaringClass = candidate.getDeclaringClass();
ClassNode actualReceiver = receiver != null ? receiver :
declaringClass;
Map<GenericsType, GenericsType> spec;
if (/*!isExtensionMethod && */candidate.isStatic()) {
spec = Collections.emptyMap(); // none visible
} else {
spec =
GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass,
actualReceiver);
GenericsType[] methodGenerics = candidate.getGenericsTypes();
if (/*!isExtensionMethod && */methodGenerics != null) { //
GROOVY-10322: remove hidden type parameters
for (int i = 0, n = methodGenerics.length; i < n &&
!spec.isEmpty(); i += 1) {
for (Iterator<GenericsType> it =
spec.keySet().iterator(); it.hasNext(); ) {
if
(it.next().getName().equals(methodGenerics[i].getName())) it.remove();
}
}
}
}
Parameter[] params = makeRawTypes(safeNode.getParameters(), spec);
int dist = measureParametersAndArgumentsDistance(params, safeArgs);
if (dist >= 0) {
if (!isExtensionMethod) { // GROOVY-6849, GROOVY-8788, et al.
dist += (1 + getClassDistance(declaringClass,
actualReceiver));
}
if (dist < bestDist) {
bestDist = dist;
bestChoices.clear();
bestChoices.add(candidate);
} else if (dist == bestDist) {
bestChoices.add(candidate);
}
}
}
if (bestChoices.size() > 1) {
// GROOVY-6849: prefer extension method in case of ambiguity
List<MethodNode> onlyExtensionMethods = new LinkedList<>();
for (MethodNode choice : bestChoices) {
if (choice instanceof ExtensionMethodNode) {
onlyExtensionMethods.add(choice);
}
}
if (onlyExtensionMethods.size() > 1) {
// GROOVY-8788: prefer closer parameter match over closer
self-type match
bestDist = Integer.MAX_VALUE; List<MethodNode> bestExtensions =
new LinkedList<>();
for (MethodNode extension : onlyExtensionMethods) { // exclude
self-type from distance checking
int dist =
measureParametersAndArgumentsDistance(extension.getParameters(), argumentTypes);
if (dist < bestDist) {
bestDist = dist;
bestExtensions.clear();
bestExtensions.add(extension);
} else if (dist == bestDist) {
bestExtensions.add(extension);
}
}
onlyExtensionMethods = bestExtensions;
}
if (onlyExtensionMethods.size() == 1) {
return onlyExtensionMethods;
}
}
return bestChoices;
}
{code}
> Inconsistency in extension method selection with @CompileStatic
> ---------------------------------------------------------------
>
> Key: GROOVY-8788
> URL: https://issues.apache.org/jira/browse/GROOVY-8788
> Project: Groovy
> Issue Type: Bug
> Components: Static compilation, Static Type Checker
> Affects Versions: 2.4.15, 2.5.2
> Reporter: Daniil Ovchinnikov
> Assignee: Eric Milles
> Priority: Major
>
> Given properly registered extension class:
> {code:java|title=MyExtensions.java}
> public class MyExtensions {
> public static void foo(Object self, String s) {
> System.out.println("Object#foo(String)");
> }
> public static void foo(String self, Object o) {
> System.out.println("String#foo(Object)");
> }
> }
> {code}
> Run
> {code:java|title=playground.groovy}
> void usageExt() {
> "".foo("") // prints "Object#foo(String)" which is correct
> }
> @groovy.transform.CompileStatic
> void usageExtStatic() {
> "".foo("") // prints "String#foo(Object)" which is questionable
> }
> usageExt()
> usageExtStatic()
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)