Paul King created GROOVY-12647:
----------------------------------

             Summary: A null Boolean or Character returned to a primitive 
functional interface method gives different results depending on the form and 
the compilation mode
                 Key: GROOVY-12647
                 URL: https://issues.apache.org/jira/browse/GROOVY-12647
             Project: Groovy
          Issue Type: Improvement
          Components: Static Type Checker
            Reporter: Paul King


Description:

When a functional interface method returns a primitive and the implementation 
returns {{null}}, the result depends on whether the implementation is a method 
pointer, closure, lambda or method reference, and on whether the code is 
dynamic, {{@TypeChecked}} or {{@CompileStatic}}.

{code:groovy}
import java.util.function.*
interface CharFn<T> { char apply(T t) }
class P { static <T> T id(T t) { t } }

// each line, in dynamic code, under @TypeChecked and under @CompileStatic
Predicate<Boolean> p = P::id;          p.test(null)
ToIntFunction<Integer> f = P::id;      f.applyAsInt(null)
CharFn<Character> c = P::id;           c.apply(null)
// and the same with P.&id, x -> x, { x -> x }, and a method: static boolean 
m(Boolean x) { x }
{code}

Results (6.0.0 and master give the same results):

|| Returned to || method {{return}} || {{.&}} or closure, any mode || lambda, 
dynamic/TC || lambda, CS || {{::}}, dynamic/TC || {{::}}, CS ||
| {{boolean}} ({{Boolean}}) | false | false | false | false | false | *NPE* |
| {{int}} ({{Integer}}) | NPE | GroovyCastException | GroovyCastException | NPE 
| GroovyCastException | NPE |
| {{char}} ({{Character}}) | NPE | '\0' | '\0' | NPE | '\0' | NPE |

Lambdas and method references are closures in dynamic and {{@TypeChecked}} 
code, and are converted at run time with Groovy's cast rules; under 
{{@CompileStatic}} they use {{LambdaMetafactory}}, which unboxes as Java does.

* {{boolean}}: a {{@CompileStatic}} method reference is the only form that 
throws. Every other form, including a {{@CompileStatic}} lambda and a method's 
{{return}}, gives {{false}} by Groovy truth.
* {{int}}: every form throws; only the exception type differs.
* {{char}}: the forms that go through a closure give {{'\0'}}, while a method's 
{{return}} and the {{@CompileStatic}} lambda and method reference throw 
{{NullPointerException}}.

So for the same source, the value can differ between {{@TypeChecked}} and 
{{@CompileStatic}}, and between a lambda and a method reference in the same 
mode.

Possible directions, for discussion:
# Leave as is, and document the differences.
# Use a method's {{return}} as the reference: {{false}} for {{boolean}} 
everywhere, and an exception for {{char}} everywhere. For a {{@CompileStatic}} 
method reference this means an adapter (Groovy truth) for a {{Boolean}} type 
argument returned to {{boolean}}, which is currently bound directly; for the 
closure-based forms it means not converting {{null}} to {{'\0'}}.
# Align on Java's behaviour (NPE) for the {{@CompileStatic}} forms only, 
accepting that dynamic code differs.

Related:
* GROOVY-12616, GROOVY-12617, GROOVY-12623 (PR #3164): keep a {{Boolean}} or 
{{Character}} type argument on {{LambdaMetafactory}}, so they leave this 
behaviour unchanged.
* GROOVY-12492 (PR #3026): an option to convert closure literals to functional 
interfaces at compile time under {{@CompileStatic}} would change the closure 
column for that mode.




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

Reply via email to