paulk-asert opened a new pull request, #3257:
URL: https://github.com/apache/groovy/pull/3257

   ## The problem
   
   When overloads take functional interfaces whose abstract methods have 
different numbers of parameters, Groovy could not use the functional argument's 
arity to choose between them, except for closures and lambdas, and then only as 
a tie-break (GROOVY-9881, GROOVY-10905, GROOVY-11121). So:
   
   - A method reference or pointer was ambiguous: `red(numbers, Integer::sum)` 
with `red(Collection, BinaryOperator)` and `red(Collection, Function)`, or 
`from(Main::m)` with `Function` and `Supplier` overloads (GROOVY-11010). Static 
type checking reported `Reference to method is ambiguous`, and dynamic code 
reported `Ambiguous method overloading`.
   - Where `Runnable` or `Callable` competed with an interface that takes 
arguments, the argument matched `Runnable` or `Callable`, because `Closure` 
implements them and they win on distance before any tie-break applies 
(GROOVY-10714). This covered `andThenTry(table::putItem)`, `andThenTry { String 
s -> … }` and `andThenTry((String s) -> …)` with `Consumer`/`Runnable` 
overloads. Under static type checking the call was then rejected. At run time 
the closure was called with `null`, and the method reference failed when run.
   
   The vavr shape in GROOVY-10714 (`CheckedConsumer`/`CheckedRunnable`) 
compiled under `@CompileStatic` on 4.0.33 but is ambiguous on 5.1.3 and 6.0.0, 
the regression reported there.
   
   ## The change
   
   As in Java (JLS 15.12.2.1), a functional argument can only implement an 
interface whose abstract method it can be called with. Before ranking 
candidates by distance, method selection now drops those whose 
functional-interface parameter takes a number of arguments the argument cannot 
accept. If none fits, all candidates are kept and selection proceeds as before.
   
   1. **`@TypeChecked`/`@CompileStatic`, method references and pointers 
(GROOVY-11010).** The argument counts come from the methods of the reference's 
name: their own parameters for a static method or a bound receiver, and one 
more for the receiver of `Type::instanceMethod`. Varargs methods accept any 
count from one fewer, and constructor references use the constructors.
   2. **Run time, method closures (GROOVY-10714).** This covers dynamic code 
and `@TypeChecked` calls, which are dispatched at run time. A `MethodClosure`'s 
class says nothing about the method it names, so a choice that depends on it is 
not cached, neither by the metaclass nor by the indy call site. That only 
happens where a method closure is passed for parameters whose functional 
interfaces differ in arity among the valid candidates, and such calls failed or 
misbehaved before. All other selections are cached as before.
      - `MethodClosure` works out its argument counts when first asked, and 
again when `rehydrate` gives it an owner of another class.
      - The counts are `transient`, so the serial form is unchanged.
      - A dehydrated method closure, whose methods are unknown, is chosen for 
as before.
   3. **Closures and lambdas with declared parameters (GROOVY-10714), static 
and dynamic.** Under static type checking the counts come from the declared 
parameters, allowing for default values and a trailing array. At run time they 
come from the class's `doCall` methods, cached per class, so these selections 
stay cacheable. Packed closures present the same view through their fixed-arity 
classes.
   4. `COMPATIBILITY.md` entry.
   
   ## What is unchanged
   
   These cases avoid the problems noted on GROOVY-10714 with re-ranking 
`Runnable`/`Callable`:
   
   - A closure with an implicit parameter (`{ … }`, `{ it }`) and `{ -> … }` 
still prefer `Runnable`/`Callable`.
   - Interfaces of the same arity, such as `Runnable` and `Callable`, are 
chosen between as before, so `executor.submit { -> … }` (GROOVY-11681) is 
unaffected.
   - Parameters of type `Closure`, `Object`, or an interface without a single 
abstract method (`Serializable`, `Cloneable`, `GroovyObject`) are never dropped.
   - A method reference whose methods accept both arities, such as 
`Integer::valueOf`, stays ambiguous.
   
   ## Compatibility
   
   - **Behaviour:** dynamic code that passes a closure with declared 
parameters, or a method reference, where `Runnable`/`Callable` competes with an 
interface of matching arity now gets that interface. The closure receives its 
arguments instead of `null`. Under static type checking those calls did not 
compile before, so no code that compiles today changes.
   - **API:** two `@since 6.0.1` static helpers on the internal 
`MetaClassHelper`. `MethodClosure` gains only package-private members, and its 
serial form is unchanged (pinned by a test).
   
   ## Tests
   
   Each new regression test fails on master and passes with the change. The few 
that pin unchanged behaviour pass on both.
   
   - `MethodReferenceTest` (`@CompileStatic`) covers:
     - each kind of reference: static, unbound instance, bound, constructor and 
`.&` pointer;
     - GROOVY-11010 `from(Main::m)` and `from(Main::n)`;
     - the GROOVY-10714 `table::putItem` shape, and the `Checked*` pair;
     - `Runnable` still preferred over `Callable`;
     - `Integer::valueOf` still ambiguous.
   - `ClosuresSTCTest`, run under `@TypeChecked` and, through 
`ClosuresStaticCompileTest`, `@CompileStatic`: `Consumer`/`Runnable` and 
`Function`/`Callable` with explicit-parameter, no-parameter and implicit 
closures, and a lambda.
   - `bugs.Groovy10714` covers run-time dispatch:
     - dynamic and `@TypeChecked` method references, and the 
`Consumer`/`Runnable` and `Checked*` shapes;
     - closures with declared and default parameters, also under 
`@PackedClosures`, with an assertion that the closure is packed;
     - one call site given references, and then closures, of different arity, 
called past the first link, so nothing is wrongly cached;
     - rehydrated and dehydrated method closures and closures;
     - the multi-arity reference;
     - the serial form of `MethodClosure`.
   
   The full root `:test` suite ran 19,777 tests with one failure, 
`TestDgmConverter.testProxyFactoryIsShardedAndReflectionFree` ("expected 2012, 
was 2010"), which fails identically on master. `./gradlew lintGate` passes.
   
   ## Not included
   
   - Interfaces of the same arity that differ only in returning a value or 
`void` (`Runnable` vs `Callable`/`Supplier` for `K::value`): Java prefers the 
value-returning one. That needs a return-compatibility check and would change 
the `Runnable`-over-`Callable` preference, so it is a separate decision.
   - `@CompileStatic` still rejects a closure with default or trailing-array 
parameters for a functional interface, as on master. Its overload is now 
selected correctly.
   - Closures returned by `memoize()`, `curry()` and `trampoline()` are not 
closure literals, so they are selected for as before. Their arity is per 
instance.
   - While checking round trips, I noticed that `MethodClosure.readObject` has 
thrown even when `MethodClosure.ALLOW_RESOLVE` is set since GROOVY-10057. So a 
method closure cannot be deserialized at all, which may or may not be intended. 
This PR does not change it.
   
   The `@CompileStatic` part (first commit) is self-contained and fixes the 4.0 
→ 5.x regression, so it could be backported on its own. The run-time parts 
change method selection and caching, so I would keep them for master.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to