Copilot commented on code in PR #2428:
URL: https://github.com/apache/groovy/pull/2428#discussion_r3050620131


##########
src/main/java/groovy/lang/GroovyShell.java:
##########
@@ -259,39 +259,29 @@ private Object runScriptOrMainOrTestOrRunnable(Class 
scriptClass, String[] args)
                 // ignore instantiation errors, try to do main
             }
         }
-        try {
-            // let's find a String[] main method
-            Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, 
String[].class);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(stringArrayMain.getModifiers())) {
-                return InvokerHelper.invokeStaticMethod(scriptClass, 
MAIN_METHOD_NAME, new Object[]{args});
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
args);
-            }
-        } catch (NoSuchMethodException ignore) { }
-        try {
-            // let's find an Object main method
-            Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, 
Object.class);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(stringArrayMain.getModifiers())) {
-                return InvokerHelper.invokeStaticMethod(scriptClass, 
MAIN_METHOD_NAME, new Object[]{args});
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
new Object[]{args});
-            }
-        } catch (NoSuchMethodException ignore) { }
-        try {
-            // let's find a no-arg main method
-            Method noArgMain = scriptClass.getMethod(MAIN_METHOD_NAME);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(noArgMain.getModifiers())) {
-                return 
InvokerHelper.invokeStaticNoArgumentsMethod(scriptClass, MAIN_METHOD_NAME);
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
EMPTY_ARGS);
+        // Select main method using JEP-512 priority: static before instance, 
args before no-args.
+        // Uses Method.invoke() directly to avoid Groovy multimethod dispatch 
selecting a different overload.
+        Method selected = findMainMethod(scriptClass);
+        if (selected != null) {
+            try {
+                selected.setAccessible(true);
+                if (Modifier.isStatic(selected.getModifiers())) {
+                    return selected.getParameterCount() == 0
+                            ? selected.invoke(null)
+                            : selected.invoke(null, (Object) args);
+                } else {
+                    Object instance = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
+                    return selected.getParameterCount() == 0
+                            ? selected.invoke(instance)
+                            : selected.invoke(instance, (Object) args);
+                }
+            } catch (InvocationTargetException e) {
+                throw e.getCause() instanceof RuntimeException re ? re
+                        : new InvokerInvocationException(e);

Review Comment:
   When `main` throws a checked exception or an `Error`, wrapping the 
`InvocationTargetException` itself can obscure the real cause. Consider (a) 
rethrowing `Error` causes directly, and (b) wrapping `e.getCause()` (not `e`) 
so stack traces and messages point at the user exception rather than 
`InvocationTargetException`.
   ```suggestion
                   Throwable cause = e.getCause();
                   if (cause instanceof RuntimeException re) {
                       throw re;
                   }
                   if (cause instanceof Error err) {
                       throw err;
                   }
                   throw new InvokerInvocationException(cause);
   ```



##########
src/main/java/org/codehaus/groovy/ast/ModuleNode.java:
##########
@@ -527,16 +530,33 @@ private MethodNode handleMainMethodIfPresent(final 
List<MethodNode> methods) {
                         if (node.isStatic() ? foundStatic : foundInstance) {
                             throw new RuntimeException("Repetitive main method 
found.");
                         }
-                        if (!foundStatic) { // static trumps instance
-                            result = node;
-                        }
+                        validMains.add(node);
 
                         if (node.isStatic()) foundStatic = true;
                         else foundInstance = true;

Review Comment:
   The `foundStatic`/`foundInstance` guard still throws when there is more than 
one valid static (or instance) `main`, which contradicts the new behavior and 
docs (multiple overloads are now expected and should be prioritized). Remove 
this guard or replace it with a check that only rejects true duplicates (same 
signature), which should typically be impossible anyway.



##########
src/main/java/org/codehaus/groovy/ast/ModuleNode.java:
##########
@@ -509,11 +509,14 @@ private MethodNode findRun() {
      * We retain the 'main' method if a compatible one is found.
      * A compatible one has no parameters or 1 (Object or String[]) parameter.
      * The return type must be void or Object.
+     * When multiple valid main methods exist, a warning is issued for those
+     * that would not be reachable from the command-line runner.
+     * Priority follows JEP-512: static before instance, args before no-args.
      */
     private MethodNode handleMainMethodIfPresent(final List<MethodNode> 
methods) {
         boolean foundInstance = false;
         boolean foundStatic = false;
-        MethodNode result = null;
+        List<MethodNode> validMains = new ArrayList<>();
         for (MethodNode node : methods) {
             if ("main".equals(node.getName()) && !node.isPrivate()) {
                 int numParams = node.getParameters().length;

Review Comment:
   The `foundStatic`/`foundInstance` guard still throws when there is more than 
one valid static (or instance) `main`, which contradicts the new behavior and 
docs (multiple overloads are now expected and should be prioritized). Remove 
this guard or replace it with a check that only rejects true duplicates (same 
signature), which should typically be impossible anyway.



##########
src/main/java/groovy/lang/GroovyShell.java:
##########
@@ -259,39 +259,29 @@ private Object runScriptOrMainOrTestOrRunnable(Class 
scriptClass, String[] args)
                 // ignore instantiation errors, try to do main
             }
         }
-        try {
-            // let's find a String[] main method
-            Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, 
String[].class);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(stringArrayMain.getModifiers())) {
-                return InvokerHelper.invokeStaticMethod(scriptClass, 
MAIN_METHOD_NAME, new Object[]{args});
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
args);
-            }
-        } catch (NoSuchMethodException ignore) { }
-        try {
-            // let's find an Object main method
-            Method stringArrayMain = scriptClass.getMethod(MAIN_METHOD_NAME, 
Object.class);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(stringArrayMain.getModifiers())) {
-                return InvokerHelper.invokeStaticMethod(scriptClass, 
MAIN_METHOD_NAME, new Object[]{args});
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
new Object[]{args});
-            }
-        } catch (NoSuchMethodException ignore) { }
-        try {
-            // let's find a no-arg main method
-            Method noArgMain = scriptClass.getMethod(MAIN_METHOD_NAME);
-            // if that main method exists, invoke it
-            if (Modifier.isStatic(noArgMain.getModifiers())) {
-                return 
InvokerHelper.invokeStaticNoArgumentsMethod(scriptClass, MAIN_METHOD_NAME);
-            } else {
-                Object script = 
InvokerHelper.invokeNoArgumentsConstructorOf(scriptClass);
-                return InvokerHelper.invokeMethod(script, MAIN_METHOD_NAME, 
EMPTY_ARGS);
+        // Select main method using JEP-512 priority: static before instance, 
args before no-args.
+        // Uses Method.invoke() directly to avoid Groovy multimethod dispatch 
selecting a different overload.
+        Method selected = findMainMethod(scriptClass);
+        if (selected != null) {
+            try {
+                selected.setAccessible(true);

Review Comment:
   `findMainMethod` uses `Class#getMethod`, which returns only public methods, 
so `setAccessible(true)` should be unnecessary. Keeping it can introduce 
avoidable failures under restrictive security/module settings; consider 
removing it (or only setting accessible when using `getDeclaredMethod`).
   ```suggestion
   
   ```



##########
src/main/java/org/codehaus/groovy/ast/ModuleNode.java:
##########
@@ -527,16 +530,33 @@ private MethodNode handleMainMethodIfPresent(final 
List<MethodNode> methods) {
                         if (node.isStatic() ? foundStatic : foundInstance) {
                             throw new RuntimeException("Repetitive main method 
found.");
                         }
-                        if (!foundStatic) { // static trumps instance
-                            result = node;
-                        }
+                        validMains.add(node);
 
                         if (node.isStatic()) foundStatic = true;
                         else foundInstance = true;
                     }
                 }
             }
         }
+
+        if (validMains.isEmpty()) return null;
+
+        // Select winner using JEP-512 priority: static before instance, args 
before no-args
+        validMains.sort((a, b) -> {
+            if (a.isStatic() != b.isStatic()) return a.isStatic() ? -1 : 1;
+            return Integer.compare(b.getParameters().length, 
a.getParameters().length);
+        });
+        MethodNode result = validMains.get(0);
+
+        // Warn about unreachable main methods
+        for (int i = 1; i < validMains.size(); i++) {
+            MethodNode unreachable = validMains.get(i);
+            getContext().addWarning("Method '" + unreachable.getText()
+                    + "' is not reachable from the Groovy runner"
+                    + " because a higher-priority main method '"
+                    + result.getText() + "' exists", unreachable);

Review Comment:
   `MethodNode.getText()` can be overly verbose and/or unstable for user-facing 
warnings (it may include more than a concise signature). Consider formatting a 
compact signature for both methods (e.g., name + parameter types + 
static/instance) to keep warnings readable and consistent.



##########
src/main/java/org/codehaus/groovy/ast/ModuleNode.java:
##########
@@ -527,16 +530,33 @@ private MethodNode handleMainMethodIfPresent(final 
List<MethodNode> methods) {
                         if (node.isStatic() ? foundStatic : foundInstance) {
                             throw new RuntimeException("Repetitive main method 
found.");
                         }
-                        if (!foundStatic) { // static trumps instance
-                            result = node;
-                        }
+                        validMains.add(node);
 
                         if (node.isStatic()) foundStatic = true;
                         else foundInstance = true;
                     }
                 }
             }
         }
+
+        if (validMains.isEmpty()) return null;
+
+        // Select winner using JEP-512 priority: static before instance, args 
before no-args
+        validMains.sort((a, b) -> {
+            if (a.isStatic() != b.isStatic()) return a.isStatic() ? -1 : 1;
+            return Integer.compare(b.getParameters().length, 
a.getParameters().length);
+        });

Review Comment:
   This comparator doesn’t implement the documented priority between 
`main(String[] args)` and `main(Object args)` because both have 
`getParameters().length == 1`. The resulting winner depends on the original 
method iteration order (and sort stability), making selection potentially 
non-deterministic and not aligned with the stated priority. Add a tie-breaker 
that ranks parameter type (`String[]` higher priority than `Object`) after the 
static/instance comparison and before the no-arg comparison.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to