[
https://issues.apache.org/jira/browse/GROOVY-12616?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18136447#comment-18136447
]
ASF GitHub Bot commented on GROOVY-12616:
-----------------------------------------
daniellansun commented on code in PR #3164:
URL: https://github.com/apache/groovy/pull/3164#discussion_r4442696024
##########
COMPATIBILITY.md:
##########
@@ -901,6 +901,55 @@ of `CompilationUnit` that adds it as a phase operation, or
that calls
`PlaceholderVisitor.isPlaceholder`. Drop the phase operation. Where code needs
to know whether a variable is `_`, check its name.
+### Groovy 7 — values returned to a primitive functional interface type are
checked as a method's return (GROOVY-12616, GROOVY-12617, GROOVY-12623)
+
+Under `@TypeChecked` and `@CompileStatic`, a value that a closure, lambda,
+method reference (`::`) or method pointer (`.&`) returns to a functional
+interface whose method returns a primitive type is now checked as the `return`
+of a method declared with that type. A number may be widened, but not narrowed
+unless it is a constant that fits; `char` is not converted to or from a number
Review Comment:
“char is not converted to or from a number type” disagrees with the int→char
bullet just below, and with `checkCompatibleAssignmentTypes` (char, Character,
or int). Suggest: char is not converted to a numeric type; only int is
converted to char. Also, under “What is unchanged”, `Supplier<Byte> s = P::v`
for an `int` method is now “Possible loss of precision”, which master accepted
for a method reference. Worth an explicit sentence either way.
##########
src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java:
##########
@@ -725,20 +740,45 @@ private GStringAdaptation adaptGStringParameters(final
FunctionalInterfaceContex
call.setSourcePosition(source);
Statement invocation = impl.isVoidMethod() ? stmt(call) :
returnS(call);
- Statement body =
block(StaticInvocationWriter.gstringBridgePrologue(controller, samParams),
invocation);
+ Statement body = any ?
block(StaticInvocationWriter.gstringBridgePrologue(controller, samParams),
invocation) : invocation;
ClassNode[] exceptions = impl.getExceptions() == null ?
ClassNode.EMPTY_ARRAY : impl.getExceptions();
ClassNode classNode = controller.getClassNode();
MethodNode adapter = classNode.addSyntheticMethod(
- createSyntheticMethodName("gstring", impl.getDeclaringClass(),
impl.getName()),
+ createSyntheticMethodName(any ? "gstring" : "adapt",
impl.getDeclaringClass(), impl.getName()),
// the JVM rejects ACC_FINAL on any method of an interface (JVMS
4.6)
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC |
(classNode.isInterface() ? 0 : Opcodes.ACC_FINAL),
- impl.getReturnType(),
+ convertReturn ? samReturnType : impl.getReturnType(),
adapterParams,
exceptions,
body);
adapter.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE,
Boolean.TRUE);
adapter.setSourcePosition(source);
- return new GStringAdaptation(adapter, samParams);
+ return new Adaptation(adapter, samParams);
+ }
+
+ /**
+ * Determines if a value of {@code returnType} must be converted to the
primitive return type
+ * of the functional interface by Groovy, because {@code
LambdaMetafactory} cannot convert it.
+ * For a declared return type, the metafactory unboxes and applies
widening primitive
+ * conversion. For a type parameter (erased to {@code Object}), it casts
to the wrapper type
+ * of {@code boolean} or {@code char}, or to {@code Number} for a numeric
type, and unboxes.
+ * Which conversions are allowed at all is decided by the type checker.
+ *
+ * @param returnType the declared return type, or the type argument for an
erased one
+ * @param erased whether the referenced method returns a type parameter
+ */
+ private static boolean isReturnConversionRequired(final ClassNode
returnType, final boolean erased, final ClassNode samReturnType) {
+ if (!ClassHelper.isPrimitiveType(samReturnType) ||
ClassHelper.isPrimitiveVoid(samReturnType)
+ || ClassHelper.isPrimitiveVoid(returnType)) {
+ return false;
+ }
+ ClassNode type = ClassHelper.getUnwrapper(returnType);
+ if (type.equals(samReturnType)) return false;
Review Comment:
This equality test is what keeps an erased `Boolean`→`boolean` and
`Character`→`char` off the adapter. The `erased` branch below returns true for
every boolean or char SAM; without this line those type arguments would be
Groovy-unboxed (`null` Boolean → false, `null` Character → `'\u0000'`) and
`testPrimitiveSAMFromTypeParameterReturnTypeNoConversion` would fail. A short
comment would stop the next edit from “simplifying” it away. I confirmed the
bytecode: `Predicate<Boolean>`, `ToLongFunction<Integer>`, and
`ToDoubleFunction<BigDecimal>` bind `C.id` directly; `Predicate<String>` uses
one `booleanUnbox` adapter.
##########
src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java:
##########
@@ -1330,6 +1330,8 @@ &&
findSAM(target).getName().equals(spec.get(0).getKeyExpression().getText())) {
inferParameterAndReturnTypesOfClosureOnRHS(target, lambda);
source.putNodeMetaData(PARAMETER_TYPE,
lambda.getNodeMetaData(PARAMETER_TYPE));
source.putNodeMetaData(CLOSURE_ARGUMENTS,
extractTypesFromParameters(lambda.getParameters()));
+ } else if (source instanceof MethodPointerExpression) { // as for
a method pointer argument
Review Comment:
`.&` only receives `PARAMETER_TYPE` here, so a method type parameter
witnessed by the SAM parameter stays a placeholder and `checkReturnValue` is
skipped (3977–3990). `ToIntFunction<Long> f = P.&id` and `new P().&me` compile
and narrow; `P::id` / `new P()::me` do not. The argument path (4727) has the
same split. Please attach the SAM parameter types for a method pointer as well,
and cover `H.&id` in `ReturnConversionConsistencyTest`. `xs.&first` on
`List<Long>` is already a precision error, via the receiver.
> STC rejects a closure or lambda that returns a primitive needing only a
> widening conversion to the SAM's primitive return type
> ------------------------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12616
> URL: https://issues.apache.org/jira/browse/GROOVY-12616
> Project: Groovy
> Issue Type: Bug
> Affects Versions: 4.0.33, 5.0.8, 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> {code:groovy}
> import java.util.function.ToLongFunction
> @groovy.transform.CompileStatic
> class C {
> static void main(args) {
> ToLongFunction<String> f = (String s) -> s.length()
> assert f.applyAsLong('abc') == 3L
> }
> }
> {code}
> Actual: {{[Static type checking] - Cannot return value of type int for lambda
> expecting long}}. The same happens under @TypeChecked, with a closure (passed
> for a ToLongFunction parameter, or a closure returning 42 assigned to a
> LongSupplier), for ToDoubleFunction from int, long or float, for char to int,
> and for Integer to long.
> Expected: compiles and runs, as in Java (JLS 5.2 allows a widening primitive
> conversion, optionally after unboxing, in an assignment context) and as with
> the method reference {{C::len}} or method pointer {{C.&len}}. Narrowing (long
> to int) should still be rejected.
> Cause: {{StaticTypeCheckingVisitor.checkReturnType}} accepts a closure or
> lambda return value only when the wrapper of the SAM return type is
> compatible with the wrapper of the value type, and Long is not compatible
> with Integer.
> Seen in 4.0.33, 5.0.8 and 6.0.0.
> PR: https://github.com/apache/groovy/pull/3142
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)