You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Jochen Theodorou (Jira)" <ji...@apache.org> on 2021/11/20 07:57:00 UTC

[jira] [Comment Edited] (GROOVY-8660) Unexpected MethodSelectionException with implicit null argument

    [ https://issues.apache.org/jira/browse/GROOVY-8660?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17446767#comment-17446767 ] 

Jochen Theodorou edited comment on GROOVY-8660 at 11/20/21, 7:56 AM:
---------------------------------------------------------------------

I just happen to find this by chance. The mechanism for foo() calling foo( x ) is an eyesore to me for a long time. Yes I wanted to remove it and most people agreed with it, though it is difficult to know if these know the implications to existing code. Anyway...

RULE1:  For varargs and calling foo(SomeClass...) we basically have the following rules from Java:

# *foo( )* -> calls foo with empty array of type SomeClass[]
# *foo( null )* -> calls foo with null
# *foo( x )* -> calls foo using the reference provided by x is x is an array and its element type is a subclass of SomeClass. Otherwise the method is not applicable
# *foo( y_1, y_2, ..., y_n )* -> calls foo wrapping all y_i in an array if all the y_i are a subclass of SomeClass. The Array will have the class SomeClass[].

RULE2: For the... let me call it null-expansion we have the following rule:
If normal method selection fails and the call is without arguments try method selection again with null as argument. In the method distance algorithm any expansion, that has to be made gives a distance penalty. That means a foo(someClass...) has a bigger distance for a call foo() than if I would call a method declared as foo() directly.  foo(null) or foo( x ) do not have that penalty, because no additional action has to be taken.

Now let's look at the cases in the issue:
{code:Java}
class OnlySingle {
    def foo(a) { "single param: $a" }
}

println new OnlySingle().foo()
// as expected: 'single param: null'
{code}
This means the initial method selection fails and we start a second method selection with foo(null), which then successfully calls the method. RULE1
{code:Java}
class Both {
    def foo(a) { "single param: $a" }
    def foo(a, ... b) { "vararg param: $a, $b" }
}

println new Both().foo()
// unexpected:
// MethodSelectionException: Could not find which method foo() to invoke from this list:
//  public java.lang.Object Both#foo(java.lang.Object)
//  public transient java.lang.Object Both#foo(java.lang.Object, [Ljava.lang.Object;)
{code}
Here actually the first method selection fails, since there is no applicable method without argument. Because of RULE2 we then try foo(null). And unlike the optimization done, this was supposed to be a full method selection. That means we are looking at foo(Object) and foo(Object,Object...) to select from. Because of RULE1 foo(Object,Object...) has a penalty compared to foo(Object), as we need to do an expansion to fit the varargs call.

This means a correct implementation should not throw an MSE, it should have selected foo(Object) with null as argument.




was (Author: blackdrag):
I just happen to find this by chance. The mechanism for foo() calling foo(x) is an eyesore to me for a long time. Yes I wanted to remove it and most people agreed with it, though it is difficult to know if these know the implications to existing code. Anyway...

RULE1:  For varargs and calling foo(SomeClass...) we basically have the following rules from Java:

# *foo( )* -> calls foo with empty array of type SomeClass[]
# *foo( null )* -> calls foo with null
# *foo( x )* -> calls foo using the reference provided by x is x is an array and its element type is a subclass of SomeClass. Otherwise the method is not applicable
# *foo( y_1, y_2, ..., y_n )* -> calls foo wrapping all y_i in an array if all the y_i are a subclass of SomeClass. The Array will have the class SomeClass[].

RULE2: For the... let me call it null-expansion we have the following rule:
If normal method selection fails and the call is without arguments try method selection again with null as argument. In the method distance algorithm any expansion, that has to be made gives a distance penalty. That means a foo(someClass...) has a bigger distance for a call foo() than if I would call a method declared as foo() directly.  foo(null) or foo(x) do not have that penalty, because no additional action has to be taken.

Now let's look at the cases in the issue:
{code:Java}
class OnlySingle {
    def foo(a) { "single param: $a" }
}

println new OnlySingle().foo()
// as expected: 'single param: null'
{code}
This means the initial method selection fails and we start a second method selection with foo(null), which then successfully calls the method. RULE1
{code:Java}
class Both {
    def foo(a) { "single param: $a" }
    def foo(a, ... b) { "vararg param: $a, $b" }
}

println new Both().foo()
// unexpected:
// MethodSelectionException: Could not find which method foo() to invoke from this list:
//  public java.lang.Object Both#foo(java.lang.Object)
//  public transient java.lang.Object Both#foo(java.lang.Object, [Ljava.lang.Object;)
{code}
Here actually the first method selection fails, since there is no applicable method without argument. Because of RULE2 we then try foo(null). And unlike the optimization done, this was supposed to be a full method selection. That means we are looking at foo(Object) and foo(Object,Object...) to select from. Because of RULE1 foo(Object,Object...) has a penalty compared to foo(Object), as we need to do an expansion to fit the varargs call.

This means a correct implementation should not throw an MSE, it should have selected foo(Object) with null as argument.



> Unexpected MethodSelectionException with implicit null argument
> ---------------------------------------------------------------
>
>                 Key: GROOVY-8660
>                 URL: https://issues.apache.org/jira/browse/GROOVY-8660
>             Project: Groovy
>          Issue Type: Bug
>    Affects Versions: 3.0.0-alpha-2, 2.4.15, 2.5.0
>            Reporter: Daniil Ovchinnikov
>            Priority: Major
>              Labels: varargs
>
> {code:groovy}
> class OnlySingle {
>     def foo(a) { "single param: $a" }
> }
> println new OnlySingle().foo()
> // as expected: 'single param: null'
> class OnlyVararg {
>     def foo(a, ... b) { "vararg param: $a, $b" }
> }
> println new OnlyVararg().foo()
> // as expected: 'MME: No signature of method: OnlyVararg.foo() is applicable for argument types: () values: []'
> class Both {
>     def foo(a) { "single param: $a" }
>     def foo(a, ... b) { "vararg param: $a, $b" }
> }
> println new Both().foo()
> // unexpected:
> // MethodSelectionException: Could not find which method foo() to invoke from this list:
> //  public java.lang.Object Both#foo(java.lang.Object)
> //  public transient java.lang.Object Both#foo(java.lang.Object, [Ljava.lang.Object;)
> {code}
> If the exception is expected then {{OnlyVararg}} case should work too.
> If the exception is unexpected then {{Both#foo(Object)}} should be selected.



--
This message was sent by Atlassian Jira
(v8.20.1#820001)