[ 
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)

Reply via email to