[
https://issues.apache.org/jira/browse/GROOVY-11737?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18071914#comment-18071914
]
ASF GitHub Bot commented on GROOVY-11737:
-----------------------------------------
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.
> Improve clarity of Groovy main method selection priority
> --------------------------------------------------------
>
> Key: GROOVY-11737
> URL: https://issues.apache.org/jira/browse/GROOVY-11737
> Project: Groovy
> Issue Type: Question
> Reporter: Georgii Ustinov
> Assignee: Paul King
> Priority: Major
>
> Good afternoon
>
> I am currently trying to support instance main methods inside IntelliJ IDEA
> groovy plugin as a part of Groovy 5 release, however, I can’t find any
> specification of how it works.
>
> I have found that the feature was implemented here -
> [https://github.com/apache/groovy/pull/1910/files].
>
> Looking into the source code -
> [https://github.com/paulk-asert/groovy/blob/90480bfade8c20f935311055e2ceb85288870605/src/main/java/groovy/lang/GroovyShell.java#L275]
> I can see the following order:
>
> 1) static void main(String[] args);
> 2) void main(String[] args);
> 3) static void main(args);
> 4) void main(args);
> 5) static void main();
> 6) void main();
>
> The problem is that it doesn’t correspond to the reality.
>
> Let’s consider the following code main.groovy
>
>
> {code:java}
> void main(args) {
> println "1"
> }
>
> static void main() {
> println "2"
> }
> {code}
>
>
> If I run
> {code:java}
> groovy main.groovy{code}
> 1 will be printed (and it looks correct according to the semantics),
> But if I change example:
>
>
> {code:java}
> void main(String[] args) {
> println "1"
> }
>
> static void main() {
> println "2"
> }
> {code}
> 2 will be printed (and thus the order in the code is violated). But if I run
> {code:java}
> groovy main.groovy someArg{code}
> , then 1 will be printed
>
> Could you, please, provide the correct main method order resolution?
--
This message was sent by Atlassian Jira
(v8.20.10#820010)