groovy

List Parameter in Enum constructor with misbehavior

Details

  • Type: Bug Bug
  • Status: Closed Closed
  • Priority: Minor Minor
  • Resolution: Fixed
  • Affects Version/s: 1.5.6
  • Fix Version/s: 1.6-rc-1, 1.5.8, 1.7-beta-1
  • Component/s: None
  • Labels:
    None
  • Number of attachments :
    1

Description

This one

enum MyEnum {
   ONE ([1,2]),
   TWO  ([3,4]);
   private final List myList
   MyEnum (List myList) {
        this.myList=myList
    }
}
MyEnum.ONE

leads to "groovy.lang.GroovyRuntimeException: Could not find matching constructor for: MyEnum(java.lang.String, java.lang.Integer, java.lang.Integer, java.lang.Integer)"

Adding "as List" to the List declaration works:

enum MyEnum {
   ONE ([1,2] as List),
   TWO  ([3,4] as List);
   private final List myList
   MyEnum (List myList) {
        this.myList=myList
    }
}
MyEnum.ONE

Why isn't "[x,y,z]" interpreted as a List in construction of enum instances?
Bug or a feature?

-----------
blackdrag: "looks like a bug for me.. please fill a report in JIRA"

Activity

Hide
Roshan Dawrani added a comment -

Just adding some details related to the issue that I looked into.

When an enum constructor is invoked with a list as "TWO([1, 2])" (enum definition below), the AST
that is being generated seems to be wrong (The problem might be in the CST itself from which AST gets created, but I couldn't understand the CST down/right details well enough to be sure).

In the AST, the initialValueExpression that is created for the enum value field node in this case is having multiple, individual org.codehaus.groovy.ast.expr.ConstantExpression (one for each list member) instead of being one org.codehaus.groovy.ast.expr.ListExpression that wraps ConstantExpression(s) inside.

As a result, what is happening is that the instead of passing [1, 2] as one argument of type list, it is passing 2 argumements - each of type int.

The code demonstrating it is below:

enum Counter{
    ONE([1:"One", 2:"Two"]), /* AST for Map has no issue, so this map gets passed correctly as a single argument */
    TWO([1, 2]), /* instead of a list of 2 elements, it gets passed as 2 individual int args */
    THREE([10, 20, 30]); /* instead of a list of 3 elements, it gets passed as 3 individual int args */

    Object myValList = []

    Counter(Object arg1){
        println "Enum constructor called with 1 argument"
        myValList << arg1
    }
    Counter(Object arg1, Object arg2){
        println "Enum constructor called with 2 arguments"
        myValList << arg1
        myValList << arg2
    }

    Counter(Object arg1, Object arg2, Object arg3){
        println "Enum constructor called with 3 arguments"
        myValList << arg1
        myValList << arg2
        myValList << arg3
    }

    String toString() {
        myValList
    }
}

println "Counter.ONE = ${Counter.ONE}"
println "Counter.TWO = ${Counter.TWO}"
println "Counter.THREE = ${Counter.THREE}"

When explicit cast "as List" is used, then enum's initialValueExpression correctly gets a CastExpression that wraps a ListExpression that wraps multiple ConstantExpression(s). Without the cast, ConstantExpression(s) do not get wrapped in ListExpression, which is causing the problem.

Show
Roshan Dawrani added a comment - Just adding some details related to the issue that I looked into. When an enum constructor is invoked with a list as "TWO([1, 2])" (enum definition below), the AST that is being generated seems to be wrong (The problem might be in the CST itself from which AST gets created, but I couldn't understand the CST down/right details well enough to be sure). In the AST, the initialValueExpression that is created for the enum value field node in this case is having multiple, individual org.codehaus.groovy.ast.expr.ConstantExpression (one for each list member) instead of being one org.codehaus.groovy.ast.expr.ListExpression that wraps ConstantExpression(s) inside. As a result, what is happening is that the instead of passing [1, 2] as one argument of type list, it is passing 2 argumements - each of type int. The code demonstrating it is below:
enum Counter{
    ONE([1:"One", 2:"Two"]), /* AST for Map has no issue, so this map gets passed correctly as a single argument */
    TWO([1, 2]), /* instead of a list of 2 elements, it gets passed as 2 individual int args */
    THREE([10, 20, 30]); /* instead of a list of 3 elements, it gets passed as 3 individual int args */

    Object myValList = []

    Counter(Object arg1){
        println "Enum constructor called with 1 argument"
        myValList << arg1
    }
    Counter(Object arg1, Object arg2){
        println "Enum constructor called with 2 arguments"
        myValList << arg1
        myValList << arg2
    }

    Counter(Object arg1, Object arg2, Object arg3){
        println "Enum constructor called with 3 arguments"
        myValList << arg1
        myValList << arg2
        myValList << arg3
    }

    String toString() {
        myValList
    }
}

println "Counter.ONE = ${Counter.ONE}"
println "Counter.TWO = ${Counter.TWO}"
println "Counter.THREE = ${Counter.THREE}"
When explicit cast "as List" is used, then enum's initialValueExpression correctly gets a CastExpression that wraps a ListExpression that wraps multiple ConstantExpression(s). Without the cast, ConstantExpression(s) do not get wrapped in ListExpression, which is causing the problem.
Hide
Roshan Dawrani added a comment -

Just to briefly summarize it in much fewer lines - the initial value expression in AST is:

1) With casting (TWO([1, 2] as List)) - CastExpression (ListExpression (ConstantExpression(1), ConstantExpression(2))) - works correctly.

2) Without casting (TWO([1, 2])) - (ConstantExpression(1), ConstantExpression(2)) - Does not work because it should be ListExpression (ConstantExpression(1), ConstantExpression(2))

Show
Roshan Dawrani added a comment - Just to briefly summarize it in much fewer lines - the initial value expression in AST is: 1) With casting (TWO([1, 2] as List)) - CastExpression (ListExpression (ConstantExpression(1), ConstantExpression(2))) - works correctly. 2) Without casting (TWO([1, 2])) - (ConstantExpression(1), ConstantExpression(2)) - Does not work because it should be ListExpression (ConstantExpression(1), ConstantExpression(2))
Hide
Roshan Dawrani added a comment -

Hi,
Attaching the patches that fix this issue in branches 1.5.8, 1.6-RC-1, 1.7-beta-1.

In case of enum constructor being passed only one list argument, the CST was being formed correctly but AST conversion had an issue in handling the list expression.

I have modified 2 files in the supplied patch:

1) org/codehaus/groovy/antlr/AntlrParserPlugin.java - Corrected the issue in handling enum taking only one list argument.

2) gls/enums/vm5/EnumTest.groovy - Added a test for this scenario.

Hope it helps.

Roshan

Show
Roshan Dawrani added a comment - Hi, Attaching the patches that fix this issue in branches 1.5.8, 1.6-RC-1, 1.7-beta-1. In case of enum constructor being passed only one list argument, the CST was being formed correctly but AST conversion had an issue in handling the list expression. I have modified 2 files in the supplied patch: 1) org/codehaus/groovy/antlr/AntlrParserPlugin.java - Corrected the issue in handling enum taking only one list argument. 2) gls/enums/vm5/EnumTest.groovy - Added a test for this scenario. Hope it helps. Roshan
Hide
blackdrag blackdrag added a comment - - edited

hmm... at last your 1.7 patch seems to be incomplete, because it fails here with:

java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at Script1.class$(Script1.groovy)
at Script1.$get$$class$ListEnum1(Script1.groovy)
at Script1.run(Script1.groovy:9)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:561)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:536)
at groovy.lang.GroovyShell$evaluate.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:43)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124)
at gls.enums.vm5.EnumTest.testEnumWithSingleListInConstructor(EnumTest.groovy:126)
Caused by: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: ListEnum1(java.lang.String, java.lang.Integer, java.lang.Integer, java.lang.Integer)
at groovy.lang.MetaClassImpl.selectConstructorAndTransformArguments(MetaClassImpl.java:1347)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.selectConstructorAndTransformArguments(ScriptBytecodeAdapter.java:237)
at ListEnum1.$INIT(Script1.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:43)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:99)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:51)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:166)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:186)
at ListEnum1.(Script1.groovy)
Show
blackdrag blackdrag added a comment - - edited hmm... at last your 1.7 patch seems to be incomplete, because it fails here with:
java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at Script1.class$(Script1.groovy)
at Script1.$get$$class$ListEnum1(Script1.groovy)
at Script1.run(Script1.groovy:9)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:561)
at groovy.lang.GroovyShell.evaluate(GroovyShell.java:536)
at groovy.lang.GroovyShell$evaluate.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:43)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:124)
at gls.enums.vm5.EnumTest.testEnumWithSingleListInConstructor(EnumTest.groovy:126)
Caused by: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: ListEnum1(java.lang.String, java.lang.Integer, java.lang.Integer, java.lang.Integer)
at groovy.lang.MetaClassImpl.selectConstructorAndTransformArguments(MetaClassImpl.java:1347)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.selectConstructorAndTransformArguments(ScriptBytecodeAdapter.java:237)
at ListEnum1.$INIT(Script1.groovy)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:234)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:43)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:99)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:51)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:166)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:186)
at ListEnum1.(Script1.groovy)
Hide
blackdrag blackdrag added a comment -

imho the exception tells me the list is still unwrapped

Show
blackdrag blackdrag added a comment - imho the exception tells me the list is still unwrapped
Hide
blackdrag blackdrag added a comment -

ah, ok, I made an error when applying the patch...

Show
blackdrag blackdrag added a comment - ah, ok, I made an error when applying the patch...
Hide
blackdrag blackdrag added a comment -

patch applied

Show
blackdrag blackdrag added a comment - patch applied

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: