[
https://issues.apache.org/jira/browse/GROOVY-8660?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel=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