[ 
https://issues.apache.org/jira/browse/GROOVY-12746?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Paul King updated GROOVY-12746:
-------------------------------
    Description: 
When an extension method declares a functional-interface parameter in terms of 
a type parameter that appears only in its {{self}} parameter, static type 
checking does not infer that type parameter from the receiver. The closure or 
lambda parameters fall back to the type parameter's bound, usually {{Object}}.

{code:groovy}
@groovy.transform.CompileStatic
void test() {
    List<Integer> numbers = [1, 2, 3]
    numbers.collectParallel { it * 2 }        // Cannot find matching method 
java.lang.Object#multiply(int)
    numbers.findAllParallel { it > 1 }        // Cannot find matching method 
java.lang.Object#compareTo(int)
    numbers.sumParallel { a, b -> a + b }     // Cannot find matching method 
java.lang.Object#plus(java.lang.Object)
}
{code}

Every method of {{ParallelCollectionExtensions}} is affected, including the 
documented {{amounts.sumParallel \{ a, b -> a + b \}}}. So is any extension 
method, in an extension module or a DGM-like class, whose functional parameter 
has no {{Closure}} overload with {{@ClosureParams}} to fall back on. Most DGM 
methods have such an overload, which is why the problem rarely shows.

The same call written as a static call infers correctly, because the receiver 
is then an ordinary argument:
{code:groovy}
ParallelCollectionExtensions.collectParallel(numbers) { it * 2 }   // OK
{code}

h3. Cause

{{StaticTypeCheckingVisitor.inferClosureParameterTypes}} infers a method's type 
parameters from explicit type arguments, then from the call's arguments, then 
falls back to their bounds. For an extension method the receiver is the 
{{self}} argument, but it is not among the call's arguments, so a type 
parameter that only {{self}} mentions is never connected to the receiver.

h3. Fix

For a non-static extension method, connect the receiver's type to the {{self}} 
parameter's type after the arguments, so the receiver fills only type 
parameters that the arguments leave unresolved. Inference that worked before is 
unchanged. Closures and lambdas that used to be typed with the bound are now 
typed from the receiver. That matters in two ways:
* *Errors:* a body that is invalid for the actual element type is now reported 
with that type, for example {{Integer#toUpperCase()}} for {{List<Integer>}} and 
{{\{ it.toUpperCase() \}}}, not {{Object#toUpperCase()}}.
* *Overloads:* a call in the body that passes the parameter to an overloaded 
method can now select a more specific overload under static compilation, such 
as {{helper(Integer)}} instead of {{helper(Object)}}. Dynamic Groovy already 
selects that overload.

> STC: type closure and lambda parameters for an extension method's functional 
> parameter from the receiver
> --------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12746
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12746
>             Project: Groovy
>          Issue Type: Bug
>            Reporter: Paul King
>            Priority: Major
>
> When an extension method declares a functional-interface parameter in terms 
> of a type parameter that appears only in its {{self}} parameter, static type 
> checking does not infer that type parameter from the receiver. The closure or 
> lambda parameters fall back to the type parameter's bound, usually {{Object}}.
> {code:groovy}
> @groovy.transform.CompileStatic
> void test() {
>     List<Integer> numbers = [1, 2, 3]
>     numbers.collectParallel { it * 2 }        // Cannot find matching method 
> java.lang.Object#multiply(int)
>     numbers.findAllParallel { it > 1 }        // Cannot find matching method 
> java.lang.Object#compareTo(int)
>     numbers.sumParallel { a, b -> a + b }     // Cannot find matching method 
> java.lang.Object#plus(java.lang.Object)
> }
> {code}
> Every method of {{ParallelCollectionExtensions}} is affected, including the 
> documented {{amounts.sumParallel \{ a, b -> a + b \}}}. So is any extension 
> method, in an extension module or a DGM-like class, whose functional 
> parameter has no {{Closure}} overload with {{@ClosureParams}} to fall back 
> on. Most DGM methods have such an overload, which is why the problem rarely 
> shows.
> The same call written as a static call infers correctly, because the receiver 
> is then an ordinary argument:
> {code:groovy}
> ParallelCollectionExtensions.collectParallel(numbers) { it * 2 }   // OK
> {code}
> h3. Cause
> {{StaticTypeCheckingVisitor.inferClosureParameterTypes}} infers a method's 
> type parameters from explicit type arguments, then from the call's arguments, 
> then falls back to their bounds. For an extension method the receiver is the 
> {{self}} argument, but it is not among the call's arguments, so a type 
> parameter that only {{self}} mentions is never connected to the receiver.
> h3. Fix
> For a non-static extension method, connect the receiver's type to the 
> {{self}} parameter's type after the arguments, so the receiver fills only 
> type parameters that the arguments leave unresolved. Inference that worked 
> before is unchanged. Closures and lambdas that used to be typed with the 
> bound are now typed from the receiver. That matters in two ways:
> * *Errors:* a body that is invalid for the actual element type is now 
> reported with that type, for example {{Integer#toUpperCase()}} for 
> {{List<Integer>}} and {{\{ it.toUpperCase() \}}}, not 
> {{Object#toUpperCase()}}.
> * *Overloads:* a call in the body that passes the parameter to an overloaded 
> method can now select a more specific overload under static compilation, such 
> as {{helper(Integer)}} instead of {{helper(Object)}}. Dynamic Groovy already 
> selects that overload.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to