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