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)