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/11/17 18:29:40 UTC

[groovy] branch master updated: GROOVY-10845: STC: `enum` constant initialization

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2095b9eb3b GROOVY-10845: STC: `enum` constant initialization
2095b9eb3b is described below

commit 2095b9eb3b73c5bcf27625395c24fa5210890443
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Nov 17 11:37:08 2022 -0600

    GROOVY-10845: STC: `enum` constant initialization
---
 .../transform/stc/StaticTypeCheckingVisitor.java   | 77 ++++++++++++++--------
 .../stc/ArraysAndCollectionsSTCTest.groovy         |  2 +-
 .../groovy/transform/stc/CoercionSTCTest.groovy    |  7 +-
 .../transform/stc/ConstructorsSTCTest.groovy       | 12 ++--
 .../groovy/transform/stc/MethodCallsSTCTest.groovy |  6 +-
 src/test/groovy/transform/stc/MiscSTCTest.groovy   | 54 ++++++++++++++-
 6 files changed, 118 insertions(+), 40 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 44e92b6ce5..0afd5ff3ec 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -1430,7 +1430,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
      * @param node      the class node for which we will try to find a matching constructor
      * @param arguments the constructor arguments
      */
-    protected MethodNode checkGroovyStyleConstructor(final ClassNode node, final ClassNode[] arguments, final ASTNode source) {
+    protected MethodNode checkGroovyStyleConstructor(final ClassNode node, final ClassNode[] arguments, final ASTNode origin) {
         if (isObjectType(node) || isDynamicTyped(node)) {
             // in that case, we are facing a list constructor assigned to a def or object
             return null;
@@ -1445,11 +1445,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 // there will be a default hash map constructor added later
                 return new ConstructorNode(Opcodes.ACC_PUBLIC, new Parameter[]{new Parameter(LinkedHashMap_TYPE, "args")}, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE);
             } else {
-                addStaticTypeError("No matching constructor found: " + prettyPrintTypeName(node) + toMethodParametersString("", arguments), source);
+                addNoMatchingMethodError(node, "<init>", arguments, origin);
                 return null;
             }
         } else if (constructors.size() > 1) {
-            addStaticTypeError("Ambiguous constructor call " + prettyPrintTypeName(node) + toMethodParametersString("", arguments), source);
+            addStaticTypeError("Ambiguous constructor call " + prettyPrintTypeName(node) + toMethodParametersString("", arguments), origin);
             return null;
         }
         return constructors.get(0);
@@ -1945,12 +1945,12 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         }
     }
 
-    private void visitInitialExpression(final Expression value, final Expression target, final ASTNode position) {
+    private void visitInitialExpression(final Expression value, final Expression target, final ASTNode origin) {
         if (value != null) {
             ClassNode lType = target.getType();
             applyTargetType(lType, value); // GROOVY-9977
 
-            typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, position));
+            typeCheckingContext.pushEnclosingBinaryExpression(assignX(target, value, origin));
 
             value.visit(this);
             ClassNode rType = getType(value);
@@ -2720,43 +2720,54 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             addStaticTypeError("cannot resolve dynamic method name at compile time.", call);
             return;
         }
+        ClassNode type = call.getOwnerType();
+        if (type.isEnum() && name.equals("$INIT")) { // GROOVY-10845: $INIT(Object[]) delegates to constructor
+            Expression target = typeCheckingContext.getEnclosingBinaryExpression().getLeftExpression();
+            ConstructorCallExpression cce = new ConstructorCallExpression(type, call.getArguments());
+            cce.setSourcePosition(((FieldExpression) target).getField());
+            visitConstructorCallExpression(cce);
+
+            MethodNode init = type.getDeclaredMethods("$INIT").get(0);
+            call.putNodeMetaData(INFERRED_TYPE, init.getReturnType());
+            call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, init);
+            return;
+        }
         if (extension.beforeMethodCall(call)) {
             extension.afterMethodCall(call);
             return;
         }
         try {
-            ClassNode receiver = call.getOwnerType();
             ArgumentListExpression argumentList = InvocationWriter.makeArgumentList(call.getArguments());
 
             boolean closuresVisited = false; // visit *after* method has been chosen
-            visitMethodCallArguments(receiver, argumentList, closuresVisited, null);
+            visitMethodCallArguments(type, argumentList, closuresVisited, null);
             ClassNode[] args = getArgumentTypes(argumentList);
 
-            List<MethodNode> mn = findMethod(receiver, name, args);
+            List<MethodNode> mn = findMethod(type, name, args);
             if (!mn.isEmpty()) {
                 if (mn.size() == 1) {
                     // GROOVY-8909, GROOVY-8961, GROOVY-9734, GROOVY-9844, GROOVY-9915, et al.
                     resolvePlaceholdersFromImplicitTypeHints(args, argumentList, mn.get(0).getParameters());
-                    typeCheckMethodsWithGenericsOrFail(receiver, args, mn.get(0), call);
+                    typeCheckMethodsWithGenericsOrFail(type, args, mn.get(0), call);
                 }
             }
             if (mn.isEmpty()) {
-                mn = extension.handleMissingMethod(receiver, name, argumentList, args, call);
+                mn = extension.handleMissingMethod(type, name, argumentList, args, call);
             }
             if (mn.isEmpty()) {
-                addNoMatchingMethodError(receiver, name, args, call);
+                addNoMatchingMethodError(type, name, args, call);
             } else {
-                mn = disambiguateMethods(mn, receiver, args, call);
+                mn = disambiguateMethods(mn, type, args, call);
                 if (mn.size() != 1) {
                     addAmbiguousErrorMessage(mn, name, args, call);
                 } else {
                     MethodNode directMethodCallCandidate = mn.get(0);
                     ClassNode returnType = getType(directMethodCallCandidate);
                     if (returnType.isUsingGenerics() && !returnType.isEnum()) {
-                        closuresVisited = true; // now visit closure/lambda arguments with selected method
-                        visitMethodCallArguments(receiver, argumentList, true, directMethodCallCandidate);
+                        closuresVisited = true; // visit closure/lambda arguments with selected method
+                        visitMethodCallArguments(type, argumentList, true, directMethodCallCandidate);
 
-                        ClassNode rt = inferReturnTypeGenerics(receiver, directMethodCallCandidate, argumentList);
+                        ClassNode rt = inferReturnTypeGenerics(type, directMethodCallCandidate, argumentList);
                         if (rt != null && implementsInterfaceOrIsSubclassOf(rt, returnType))
                             returnType = rt;
                     }
@@ -2767,7 +2778,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
             MethodNode target = call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
             if (!closuresVisited) {
-                visitMethodCallArguments(receiver, argumentList, true, target);
+                visitMethodCallArguments(type, argumentList, true, target);
             }
             if (target != null) { Parameter[] params = target.getParameters();
                 checkClosureMetadata(argumentList.getExpressions(), params);
@@ -5898,29 +5909,41 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     @Override
-    public void addError(final String msg, final ASTNode expr) {
-        Long err = ((long) expr.getLineNumber()) << 16 + expr.getColumnNumber();
-        if ((DEBUG_GENERATED_CODE && expr.getLineNumber() < 0) || !typeCheckingContext.reportedErrors.contains(err)) {
-            typeCheckingContext.getErrorCollector().addErrorAndContinue(msg + '\n', expr, getSourceUnit());
+    public void addError(final String msg, final ASTNode node) {
+        Long err = ((long) node.getLineNumber()) << 16 + node.getColumnNumber();
+        if ((DEBUG_GENERATED_CODE && node.getLineNumber() < 0) || !typeCheckingContext.reportedErrors.contains(err)) {
+            typeCheckingContext.getErrorCollector().addErrorAndContinue(msg + '\n', node, getSourceUnit());
             typeCheckingContext.reportedErrors.add(err);
         }
     }
 
-    protected void addStaticTypeError(final String msg, final ASTNode expr) {
-        if (expr.getColumnNumber() > 0 && expr.getLineNumber() > 0) {
-            addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + msg, expr);
+    protected void addStaticTypeError(final String msg, final ASTNode node) {
+        if (node.getColumnNumber() > 0 && node.getLineNumber() > 0) {
+            addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + msg, node);
         } else {
             if (DEBUG_GENERATED_CODE) {
-                addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + "Error in generated code [" + expr.getText() + "] - " + msg, expr);
+                addError(StaticTypesTransformation.STATIC_ERROR_PREFIX + "Error in generated code [" + node.getText() + "] - " + msg, node);
             }
             // ignore errors which are related to unknown source locations
             // because they are likely related to generated code
         }
     }
 
-    protected void addNoMatchingMethodError(final ClassNode receiver, final String name, final ClassNode[] args, final Expression call) {
-        ClassNode type = isClassClassNodeWrappingConcreteType(receiver) ? receiver.getGenericsTypes()[0].getType() : receiver;
-        addStaticTypeError("Cannot find matching method " + prettyPrintTypeName(type) + "#" + toMethodParametersString(name, args) + ". Please check if the declared type is correct and if the method exists.", call);
+    protected void addNoMatchingMethodError(final ClassNode receiver, final String name, ClassNode[] args, final Expression exp) {
+        addNoMatchingMethodError(receiver, name, args, (ASTNode)exp);
+    }
+
+    protected void addNoMatchingMethodError(final ClassNode receiver, final String name, ClassNode[] args, final ASTNode origin) {
+        String error;
+        if ("<init>".equals(name)) {
+            // remove implicit agruments [String, int] from enum constant construction
+            if (receiver.isEnum() && args.length >= 2) args = Arrays.copyOfRange(args, 2, args.length);
+            error = "Cannot find matching constructor " + prettyPrintTypeName(receiver) + toMethodParametersString("", args);
+        } else {
+            ClassNode type = isClassClassNodeWrappingConcreteType(receiver) ? receiver.getGenericsTypes()[0].getType() : receiver;
+            error = "Cannot find matching method " + prettyPrintTypeName(type) + "#" + toMethodParametersString(name, args) + ". Please check if the declared type is correct and if the method exists.";
+        }
+        addStaticTypeError(error, origin);
     }
 
     protected void addAmbiguousErrorMessage(final List<MethodNode> foundMethods, final String name, final ClassNode[] args, final Expression expr) {
diff --git a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index 12319efa81..ea66af7699 100644
--- a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -1085,6 +1085,6 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
             interface MVM<K, V> extends Map<K, List<V>> { }
             MVM map = [:] // no STC error; fails at runtime
         ''',
-        'No matching constructor found'
+        'Cannot find matching constructor MVM(java.util.LinkedHashMap)'
     }
 }
diff --git a/src/test/groovy/transform/stc/CoercionSTCTest.groovy b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
index 35f6a1be2c..7e9c0ac8d2 100644
--- a/src/test/groovy/transform/stc/CoercionSTCTest.groovy
+++ b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
@@ -30,8 +30,7 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
             } catch (Throwable t) {
                 def newTrace = []
                 def clean = newTrace.toArray(newTrace as StackTraceElement[])
-                // doing twice, because bug showed that the more you call the array coercion, the more the error
-                // gets stupid :
+                // doing twice, because bug showed that the more you call the array coercion, the more the error gets stupid:
                 // Cannot call java.util.List#toArray([Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
                 // Cannot call java.util.List#toArray([[Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
                 // Cannot call java.util.List#toArray([[[Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
@@ -103,10 +102,10 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
         '''
         shouldFailWithMessages '''
             Class c = []
-        ''', 'No matching constructor found: java.lang.Class()'
+        ''', 'Cannot find matching constructor java.lang.Class()'
         shouldFailWithMessages '''
             Class c = [:]
-        ''', 'No matching constructor found: java.lang.Class(java.util.LinkedHashMap)'
+        ''', 'Cannot find matching constructor java.lang.Class(java.util.LinkedHashMap)'
     }
 
     // GROOVY-6803
diff --git a/src/test/groovy/transform/stc/ConstructorsSTCTest.groovy b/src/test/groovy/transform/stc/ConstructorsSTCTest.groovy
index 353b0cc4b2..f7566fa789 100644
--- a/src/test/groovy/transform/stc/ConstructorsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ConstructorsSTCTest.groovy
@@ -39,7 +39,8 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             import java.awt.Dimension
             Dimension d = [100]
-        ''', 'No matching constructor found: java.awt.Dimension(int)'
+        ''',
+        'Cannot find matching constructor java.awt.Dimension(int)'
     }
 
     void testWrongNumberOfArgumentsWithDefaultConstructor() {
@@ -49,7 +50,8 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
               new X("f")
             }
             println foo()
-        ''', 'Cannot find matching method X#<init>(java.lang.String)'
+        ''',
+        'Cannot find matching constructor X(java.lang.String)'
     }
 
     void testCreateArrayWithDefaultConstructor() {
@@ -64,7 +66,8 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
         shouldFailWithMessages '''
             import java.awt.Dimension
             Dimension d = ['100','200']
-        ''', 'No matching constructor found: java.awt.Dimension(java.lang.String, java.lang.String)'
+        ''',
+        'Cannot find matching constructor java.awt.Dimension(java.lang.String, java.lang.String)'
     }
 
     void testConstructFromListAndVariables() {
@@ -93,7 +96,8 @@ class ConstructorsSTCTest extends StaticTypeCheckingTestCase {
             import java.awt.Dimension
             List args = [100,200]
             Dimension d = args // not supported
-        ''', 'Cannot assign value of type java.util.ArrayList<java.lang.Integer> to variable of type java.awt.Dimension'
+        ''',
+        'Cannot assign value of type java.util.ArrayList<java.lang.Integer> to variable of type java.awt.Dimension'
     }
 
     void testConstructFromMap() {
diff --git a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
index c594abeaea..7172bb6c17 100644
--- a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
@@ -389,7 +389,7 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase {
                 def foo() { new Main() }
             }
         ''',
-        'Cannot find matching method Main#<init>()'
+        'Cannot find matching constructor Main()'
     }
 
     // GROOVY-8509
@@ -1257,8 +1257,8 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase {
             }
             new C(*['A','B'])
         ''',
-        'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
-        'Cannot find matching method '
+        'Cannot find matching constructor C(',
+        'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time'
     }
 
     void testSpreadArgsRestrictedInClosureCall() {
diff --git a/src/test/groovy/transform/stc/MiscSTCTest.groovy b/src/test/groovy/transform/stc/MiscSTCTest.groovy
index 9bde8b8185..fe77518b9f 100644
--- a/src/test/groovy/transform/stc/MiscSTCTest.groovy
+++ b/src/test/groovy/transform/stc/MiscSTCTest.groovy
@@ -205,11 +205,63 @@ class MiscSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10845
+    void testEnumConstructorChecks() {
+        shouldFailWithMessages '''
+            enum E {
+                CONST()
+                E(String s) { }
+            }
+        ''',
+        'Cannot find matching constructor E()'
+
+        shouldFailWithMessages '''
+            enum E {
+                CONST(new Object())
+                E(String s) { }
+            }
+        ''',
+        'Cannot find matching constructor E(java.lang.Object)'
+
+        shouldFailWithMessages '''
+            enum E {
+                CONST(new Object())
+            }
+        ''',
+        'Cannot find matching constructor E(java.lang.Object)'
+
+        assertScript '''
+            @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                def init = node.getDeclaredMethod("<clinit>").code.statements[0] // this.CONST = $INIT("CONST",0,"xx")
+                def dmct = init.expression.rightExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)
+                assert dmct != null
+                def type = init.expression.rightExpression.getNodeMetaData(INFERRED_TYPE)
+                assert type == node
+            })
+            enum E {
+                CONST('xx')
+                E(String s) { assert s == 'xx' }
+            }
+            E.CONST
+        '''
+
+        assertScript '''import groovy.transform.stc.SimpleType
+            enum E {
+                CONST({ assert it.toLowerCase() == 'const' })
+                E(@ClosureParams(value=SimpleType, options='java.lang.String') Closure c) {
+                    c.call(name())
+                }
+            }
+            E.CONST
+        '''
+    }
+
     void testMethodReturnTypeInferenceShouldNotWorkBecauseNotSameSourceUnit() {
         shouldFailWithMessages '''
             import groovy.transform.stc.MiscSTCTest.MiscSTCTestSupport as A
             A.foo().toInteger()
-        ''', 'Cannot find matching method java.lang.Object#toInteger()'
+        ''',
+        'Cannot find matching method java.lang.Object#toInteger()'
     }
 
     void testClassLiteralAsArgument() {