You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/06/22 16:51:00 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10660: STC: infer closure parameter types from method return type

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new c232a58b5e GROOVY-10660: STC: infer closure parameter types from method return type
c232a58b5e is described below

commit c232a58b5eb6b42db71b874b4e6c5ac387336f50
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Jun 22 10:16:21 2022 -0500

    GROOVY-10660: STC: infer closure parameter types from method return type
    
    4_0_X backport
---
 .../transform/stc/StaticTypeCheckingVisitor.java   | 74 +++++++++++-----------
 .../stc/ClosureParamTypeInferenceSTCTest.groovy    | 37 +++++++++++
 2 files changed, 75 insertions(+), 36 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index bebfef2e1c..3ceff46d76 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -363,16 +363,13 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
     public static final Statement GENERATED_EMPTY_STATEMENT = EmptyStatement.INSTANCE;
 
-    protected final ReturnAdder.ReturnStatementListener returnListener = new ReturnAdder.ReturnStatementListener() {
-        @Override
-        public void returnStatementAdded(final ReturnStatement returnStatement) {
-            if (isNullConstant(returnStatement.getExpression())) return;
-            ClassNode returnType = checkReturnType(returnStatement);
-            if (typeCheckingContext.getEnclosingClosure() != null) {
-                addClosureReturnType(returnType);
-            } else if (typeCheckingContext.getEnclosingMethod() == null) {
-                throw new GroovyBugError("Unexpected return statement at " + returnStatement.getLineNumber() + ":" + returnStatement.getColumnNumber() + " " + returnStatement.getText());
-            }
+    protected final ReturnAdder.ReturnStatementListener returnListener = returnStatement -> {
+        if (returnStatement.isReturningNullOrVoid()) return;
+        ClassNode returnType = checkReturnType(returnStatement);
+        if (this.typeCheckingContext.getEnclosingClosure() != null) {
+            addClosureReturnType(returnType);
+        } else if (this.typeCheckingContext.getEnclosingMethod() == null) {
+            throw new GroovyBugError("Unexpected return statement at " + returnStatement.getLineNumber() + ":" + returnStatement.getColumnNumber() + " " + returnStatement.getText());
         }
     };
 
@@ -781,11 +778,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 } else {
                     lType = getOriginalDeclarationType(leftExpression);
 
-                    if (isFunctionalInterface(lType)) {
-                        processFunctionalInterfaceAssignment(lType, rightExpression);
-                    } else if (isClosureWithType(lType) && rightExpression instanceof ClosureExpression) {
-                        storeInferredReturnType(rightExpression, getCombinedBoundType(lType.getGenericsTypes()[0]));
-                    }
+                    applyTargetType(lType, rightExpression);
                 }
                 rightExpression.visit(this);
             }
@@ -950,15 +943,22 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         }
     }
 
-    private void processFunctionalInterfaceAssignment(final ClassNode lhsType, final Expression rhsExpression) {
-        if (rhsExpression instanceof ClosureExpression) {
-            inferParameterAndReturnTypesOfClosureOnRHS(lhsType, (ClosureExpression) rhsExpression);
-        } else if (rhsExpression instanceof MethodReferenceExpression) {
-            LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(lhsType);
+    private void applyTargetType(final ClassNode target, final Expression source) {
+        if (isFunctionalInterface(target)) {
+            if (source instanceof ClosureExpression) {
+                inferParameterAndReturnTypesOfClosureOnRHS(target, (ClosureExpression) source);
+            } else if (source instanceof MethodReferenceExpression) {
+                LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(target);
 
-            inferParameterAndReturnTypesOfClosureOnRHS(lhsType, lambdaExpression);
-            rhsExpression.putNodeMetaData(CONSTRUCTED_LAMBDA_EXPRESSION, lambdaExpression);
-            rhsExpression.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(lambdaExpression.getParameters()).map(Parameter::getType).toArray(ClassNode[]::new));
+                inferParameterAndReturnTypesOfClosureOnRHS(target, lambdaExpression);
+                source.putNodeMetaData(CONSTRUCTED_LAMBDA_EXPRESSION, lambdaExpression);
+                source.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(lambdaExpression.getParameters()).map(Parameter::getType).toArray(ClassNode[]::new));
+            }
+        } else if (isClosureWithType(target)) {
+            if (source instanceof ClosureExpression) {
+                GenericsType returnType = target.getGenericsTypes()[0];
+                storeInferredReturnType(source, getCombinedBoundType(returnType));
+            }
         }
     }
 
@@ -1936,11 +1936,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) {
         if (value != null) {
             ClassNode lType = target.getType();
-            if (isFunctionalInterface(lType)) { // GROOVY-9977
-                processFunctionalInterfaceAssignment(lType, value);
-            } else if (isClosureWithType(lType) && value instanceof ClosureExpression) {
-                storeInferredReturnType(value, getCombinedBoundType(lType.getGenericsTypes()[0]));
-            }
+            applyTargetType(lType, value); // GROOVY-9977
 
             typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, position));
 
@@ -2210,6 +2206,12 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
     @Override
     public void visitReturnStatement(final ReturnStatement statement) {
+        if (typeCheckingContext.getEnclosingClosure() == null) {
+            MethodNode method = typeCheckingContext.getEnclosingMethod();
+            if (method != null && !method.isVoidMethod() && !method.isDynamicReturnType()) {
+                applyTargetType(method.getReturnType(), statement.getExpression()); // GROOVY-10660
+            }
+        }
         super.visitReturnStatement(statement);
         returnListener.returnStatementAdded(statement);
     }
@@ -2597,6 +2599,10 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     @Override
     protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
         typeCheckingContext.pushEnclosingMethod(node);
+        final ClassNode returnType = node.getReturnType(); // GROOVY-10660: implicit return case
+        if (!isConstructor && (isClosureWithType(returnType) || isFunctionalInterface(returnType))) {
+            new ReturnAdder(returnStmt -> applyTargetType(returnType, returnStmt.getExpression())).visitMethod(node);
+        }
         readClosureParameterAnnotation(node); // GROOVY-6603
         super.visitConstructorOrMethod(node, isConstructor);
         if (node.hasDefaultValue()) {
@@ -4130,18 +4136,14 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
     @Override
     public void visitCastExpression(final CastExpression expression) {
-        ClassNode type = expression.getType();
+        ClassNode target = expression.getType();
         Expression source = expression.getExpression();
-        if (isFunctionalInterface(type)) { // GROOVY-9997
-            processFunctionalInterfaceAssignment(type, source);
-        } else if (isClosureWithType(type) && source instanceof ClosureExpression) {
-            storeInferredReturnType(source, getCombinedBoundType(type.getGenericsTypes()[0]));
-        }
+        applyTargetType(target, source); // GROOVY-9997
 
         source.visit(this);
 
-        if (!expression.isCoerce() && !checkCast(type, source)) {
-            addStaticTypeError("Inconvertible types: cannot cast " + prettyPrintType(getType(source)) + " to " + prettyPrintType(type), expression);
+        if (!expression.isCoerce() && !checkCast(target, source)) {
+            addStaticTypeError("Inconvertible types: cannot cast " + prettyPrintType(getType(source)) + " to " + prettyPrintType(target), expression);
         }
     }
 
diff --git a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
index 26851a3962..88a1e27744 100644
--- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
@@ -1474,4 +1474,41 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase {
             }
         '''
     }
+
+    void testGroovy10660() {
+        assertScript '''import java.util.function.BiConsumer
+
+            <T> BiConsumer<String, List<T>> m(BiConsumer<String, ? super T> proc) {
+                // batch processor:
+                return { text, list ->
+                    for (item in list) proc.accept(text, item)
+                }
+            }
+
+            m { text, item -> }
+        '''
+
+        assertScript '''import java.util.function.BiConsumer
+
+            <T> BiConsumer<String, List<T>> m(BiConsumer<String, ? super T> proc) {
+                // batch processor:
+                return (text, list) -> {
+                    for (item in list) proc.accept(text, item)
+                }
+            }
+
+            m((text, item) -> { })
+        '''
+
+        assertScript '''import java.util.function.BiConsumer
+
+            <T> BiConsumer<String, List<T>> m(BiConsumer<String, ? super T> proc) {
+                /*implicit return*/ (text, list) -> {
+                    for (item in list) proc.accept(text, item)
+                }
+            }
+
+            m((text, item) -> { })
+        '''
+    }
 }