[
https://issues.apache.org/jira/browse/GROOVY-12214?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Paul King updated GROOVY-12214:
-------------------------------
Description:
h3. Summary
Under {{@CompileStatic}}, a method reference ({{::}}) argument fails to resolve
to a functional-interface overload when a {{Closure}} overload of the same
method also exists. Static overload selection binds the method reference to the
{{Closure}} overload and then rejects it, because a method reference can never
coerce to {{groovy.lang.Closure}}.
This affects the "fat-free" DGM methods introduced in GROOVY-12054
(functional-interface twins of Closure-taking methods, e.g. {{collect(Iterable,
Function)}} beside {{collect(Iterable, Closure)}}): the ergonomic
method-reference spelling does not compile under {{@CompileStatic}}.
h3. Steps to reproduce
{code:groovy}
import groovy.transform.CompileStatic
@CompileStatic
List<String> upper(List<String> list) {
list.collect(String::toUpperCase)
}
assert upper(['a', 'bb']) == ['A', 'BB']
{code}
Fails to compile with:
{noformat}
[Static type checking] - Argument is a method reference, but parameter type
'groovy.lang.Closure' is not a functional interface
{noformat}
The same failure occurs for any DGM method that has both a {{Closure}} overload
and a functional-interface twin, e.g. {{[1, 2, 3].each(result::add)}}.
h3. Notes
* It works as expected *dynamically* (without {{@CompileStatic}}), and under
{{@CompileStatic}} via an explicitly-typed functional value or a cast:
{{collect(String::toUpperCase as Function)}}.
* A closure block and a lambda literal ({{s -> s}}) correctly bind to the
{{Closure}} overload; only method references are affected.
* The {{each(result::add)}} example in GROOVY-12054's {{LambdasTest}} passes
only because that test runs dynamically.
h3. Root cause
At overload-selection time a method reference presents as plain
{{groovy.lang.Closure}} (the {{MethodPointerExpression}} default type; functors
are visited *after* method selection). In
{{StaticTypeCheckingSupport.allParametersAndArgumentsMatch}}, a {{Closure}}
argument matching a {{Closure}} parameter is {{equals}} (distance 0), beating
the {{Closure}}-to-SAM distance of 13, so the {{Closure}} overload wins; the
deferred functor visit then rejects the method reference.
h3. Fix
Mark a method-reference argument's type with a new
{{StaticTypesMarker.METHOD_REFERENCE_TYPE}} in {{getArgumentTypes}}, and in
{{allParametersAndArgumentsMatch}} strongly disfavour a {{Closure}} parameter
for such an argument so a functional-interface overload is preferred. Only
{{::}} is marked; closures, {{->}} lambdas and {{.&}} method pointers are
unaffected. No regression is possible: the affected pairing is a hard compile
error today, so the only behaviour that changes is turning that error into
correct selection of the functional-interface overload. When no
functional-interface twin exists, the {{Closure}} overload remains the sole
candidate and still reports the same clear error.
> STC: method reference should select the functional-interface overload over a
> Closure overload
> ---------------------------------------------------------------------------------------------
>
> Key: GROOVY-12214
> URL: https://issues.apache.org/jira/browse/GROOVY-12214
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Assignee: Paul King
> Priority: Major
>
> h3. Summary
> Under {{@CompileStatic}}, a method reference ({{::}}) argument fails to
> resolve to a functional-interface overload when a {{Closure}} overload of the
> same method also exists. Static overload selection binds the method reference
> to the {{Closure}} overload and then rejects it, because a method reference
> can never coerce to {{groovy.lang.Closure}}.
> This affects the "fat-free" DGM methods introduced in GROOVY-12054
> (functional-interface twins of Closure-taking methods, e.g.
> {{collect(Iterable, Function)}} beside {{collect(Iterable, Closure)}}): the
> ergonomic method-reference spelling does not compile under {{@CompileStatic}}.
> h3. Steps to reproduce
> {code:groovy}
> import groovy.transform.CompileStatic
> @CompileStatic
> List<String> upper(List<String> list) {
> list.collect(String::toUpperCase)
> }
> assert upper(['a', 'bb']) == ['A', 'BB']
> {code}
> Fails to compile with:
> {noformat}
> [Static type checking] - Argument is a method reference, but parameter type
> 'groovy.lang.Closure' is not a functional interface
> {noformat}
> The same failure occurs for any DGM method that has both a {{Closure}}
> overload and a functional-interface twin, e.g. {{[1, 2,
> 3].each(result::add)}}.
> h3. Notes
> * It works as expected *dynamically* (without {{@CompileStatic}}), and under
> {{@CompileStatic}} via an explicitly-typed functional value or a cast:
> {{collect(String::toUpperCase as Function)}}.
> * A closure block and a lambda literal ({{s -> s}}) correctly bind to the
> {{Closure}} overload; only method references are affected.
> * The {{each(result::add)}} example in GROOVY-12054's {{LambdasTest}} passes
> only because that test runs dynamically.
> h3. Root cause
> At overload-selection time a method reference presents as plain
> {{groovy.lang.Closure}} (the {{MethodPointerExpression}} default type;
> functors are visited *after* method selection). In
> {{StaticTypeCheckingSupport.allParametersAndArgumentsMatch}}, a {{Closure}}
> argument matching a {{Closure}} parameter is {{equals}} (distance 0), beating
> the {{Closure}}-to-SAM distance of 13, so the {{Closure}} overload wins; the
> deferred functor visit then rejects the method reference.
> h3. Fix
> Mark a method-reference argument's type with a new
> {{StaticTypesMarker.METHOD_REFERENCE_TYPE}} in {{getArgumentTypes}}, and in
> {{allParametersAndArgumentsMatch}} strongly disfavour a {{Closure}} parameter
> for such an argument so a functional-interface overload is preferred. Only
> {{::}} is marked; closures, {{->}} lambdas and {{.&}} method pointers are
> unaffected. No regression is possible: the affected pairing is a hard compile
> error today, so the only behaviour that changes is turning that error into
> correct selection of the functional-interface overload. When no
> functional-interface twin exists, the {{Closure}} overload remains the sole
> candidate and still reports the same clear error.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)