groovy
  1. groovy
  2. GROOVY-3278

Using referenced String constant as value of Annotation causes compile error

    Details

    • Type: Bug Bug
    • Status: Open Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 1.6-rc-1
    • Fix Version/s: 2.x
    • Component/s: Compiler
    • Labels:
      None
    • Environment:
      OSX 10.5.6
      JDK 1.6
    • Number of attachments :
      2

      Description

      When using a String constant as the value for an annotation groovyc errors out with the following message:

      Attribute 'value' should have type java.lang.String but found type java.lang.Object in @SpringBean

      I've attached sample code to reproduce. The annotation and PersonDao are regular Java classes, just as was my case when finding the bug. The PersonAction is a Groovy class.

        Issue Links

          Activity

          Hide
          Roshan Dawrani added a comment -

          As per http://www.nabble.com/Annotation-values-in-Groovy.-td20824889.html and looking at the code, it looks like annotation values in groovy can only be constant values (like Strings, classes, numbers, other annotations and enums, etc).

          In this case, annotation value itself is a property expression PersonDao.DAO_ID, which is pointing to a static final String but in terms of groovy internals, it is not a constant expression but a property expression.

          So, should this case be seen as a defect or as designed?

          Show
          Roshan Dawrani added a comment - As per http://www.nabble.com/Annotation-values-in-Groovy.-td20824889.html and looking at the code, it looks like annotation values in groovy can only be constant values (like Strings, classes, numbers, other annotations and enums, etc). In this case, annotation value itself is a property expression PersonDao.DAO_ID, which is pointing to a static final String but in terms of groovy internals, it is not a constant expression but a property expression. So, should this case be seen as a defect or as designed?
          Hide
          Paul King added a comment -

          Not sure it matters whether we call it a bug or enhancement request. It isn't supported now and seems a very worthwhile (necessary?) enhancement.

          Show
          Paul King added a comment - Not sure it matters whether we call it a bug or enhancement request. It isn't supported now and seems a very worthwhile (necessary?) enhancement.
          Hide
          Gregg Bolinger added a comment -

          I was thinking the same thing as Paul, FWIW

          Show
          Gregg Bolinger added a comment - I was thinking the same thing as Paul, FWIW
          Hide
          Roshan Dawrani added a comment -

          You are right. It does look a worthwhile feature but if it more like an enhancement, then it may not be scheduled for 1.6, right?
          Or, enhancements are still being taken up for 1.6?

          Show
          Roshan Dawrani added a comment - You are right. It does look a worthwhile feature but if it more like an enhancement, then it may not be scheduled for 1.6, right? Or, enhancements are still being taken up for 1.6?
          Hide
          blackdrag blackdrag added a comment -

          It can be seen as improvement or as bug. But knowing that the groovy compiler does no form of inlining atm and this issue means we have to add that I would say it should not go into 1.6.0, but 1.6.1 might be ok

          Show
          blackdrag blackdrag added a comment - It can be seen as improvement or as bug. But knowing that the groovy compiler does no form of inlining atm and this issue means we have to add that I would say it should not go into 1.6.0, but 1.6.1 might be ok
          Hide
          Guillaume Laforge added a comment -

          It is a bug, not an improvement
          I haven't double checked how it's working in Java, but since the PersonDao.DAO_ID field is a String constant, it should be allowed in Java.
          And hence in Groovy.
          I wish it could be fixed in 1.6.0, but if it's not, it should really be fixed in 1.6.1.

          Show
          Guillaume Laforge added a comment - It is a bug, not an improvement I haven't double checked how it's working in Java, but since the PersonDao.DAO_ID field is a String constant, it should be allowed in Java. And hence in Groovy. I wish it could be fixed in 1.6.0, but if it's not, it should really be fixed in 1.6.1.
          Hide
          Roshan Dawrani added a comment -

          It works in Java. I had double checked that earlier but then I saw that mailing list thread reply and thought that groovy annotations had some known constraints and were not as flexible as Java.

          Show
          Roshan Dawrani added a comment - It works in Java. I had double checked that earlier but then I saw that mailing list thread reply and thought that groovy annotations had some known constraints and were not as flexible as Java.
          Hide
          Guillaume Laforge added a comment -

          Groovy's annotations should work as much as possible as in Java.
          But it doesn't prevent us from providing additional capabilities
          But at least, here, I think it should behave just as in Java.

          Show
          Guillaume Laforge added a comment - Groovy's annotations should work as much as possible as in Java. But it doesn't prevent us from providing additional capabilities But at least, here, I think it should behave just as in Java.
          Hide
          Paul King added a comment -

          I think I agree with Guillaume's last comment. But just so that we all understand the implications. Consider this code example:

          import java.lang.annotation.*
          
          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.TYPE)
          @interface MyAnnotation {
              String[] groups() default []
          }
          
          class Base {
              static final String CONST = 'bing'
              def run() { getClass().annotations[0].groups() }
          }
          
          @MyAnnotation(groups = 'foo')
          class Child1 extends Base {}
          
          @MyAnnotation(groups = ['bar', 'baz', Base.CONST]) //  *1
          class Child2 extends Base {}
          
          assert new Child1().run() == ['foo']
          assert new Child2().run() == ['bar', 'baz']
          

          Currently *1 breaks because we are expecting a static String but get a PropertyExpression that would evaluate at runtime to a constant.
          The implication if we somehow evaluate this is that we are bypassing any runtime behavior, i.e.
          if Base had any MOP in place which changed the value of CONST at runtime, that would be ignored here.
          I believe that is the right thing. Just confirming everyone's opinions.

          Show
          Paul King added a comment - I think I agree with Guillaume's last comment. But just so that we all understand the implications. Consider this code example: import java.lang.annotation.* @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ interface MyAnnotation { String [] groups() default [] } class Base { static final String CONST = 'bing' def run() { getClass().annotations[0].groups() } } @MyAnnotation(groups = 'foo') class Child1 extends Base {} @MyAnnotation(groups = ['bar', 'baz', Base.CONST]) // *1 class Child2 extends Base {} assert new Child1().run() == ['foo'] assert new Child2().run() == ['bar', 'baz'] Currently *1 breaks because we are expecting a static String but get a PropertyExpression that would evaluate at runtime to a constant. The implication if we somehow evaluate this is that we are bypassing any runtime behavior, i.e. if Base had any MOP in place which changed the value of CONST at runtime, that would be ignored here. I believe that is the right thing. Just confirming everyone's opinions.
          Hide
          Paul King added a comment -

          Probably a duplicate

          Show
          Paul King added a comment - Probably a duplicate
          Hide
          blackdrag blackdrag added a comment -

          isn't that equal to a problem you showed me in Paris? Where we found that we cannot access the constant value through reflection without initializing the class? And where we said that we maybe have to say, that we don't support that particular part? So from the language side it would be good to have that, but from the technical side I don't know how to implement that. As of runtime vs. compile time... I agree with Paul here

          Show
          blackdrag blackdrag added a comment - isn't that equal to a problem you showed me in Paris? Where we found that we cannot access the constant value through reflection without initializing the class? And where we said that we maybe have to say, that we don't support that particular part? So from the language side it would be good to have that, but from the technical side I don't know how to implement that. As of runtime vs. compile time... I agree with Paul here
          Hide
          Paul King added a comment -

          I think the attached patch will cover most cases but there is quite likely a better way to do it. As per the issue Jochen and I discussed in Paris, you can't wait until (say the ExtendedVerifier) and do checking in one spot because by then 'constants' are actually pulled out of 'fields' and placed into a static initializer. This meant that I had to try 'statically resolving' the constants earlier. I did it in ResolveVisitor and StaticImportVisitor. Happy for other suggestions. If I don't get feedback in the next couple of days, I will apply as is and we can tweak later if need be.

          Show
          Paul King added a comment - I think the attached patch will cover most cases but there is quite likely a better way to do it. As per the issue Jochen and I discussed in Paris, you can't wait until (say the ExtendedVerifier) and do checking in one spot because by then 'constants' are actually pulled out of 'fields' and placed into a static initializer. This meant that I had to try 'statically resolving' the constants earlier. I did it in ResolveVisitor and StaticImportVisitor. Happy for other suggestions. If I don't get feedback in the next couple of days, I will apply as is and we can tweak later if need be.
          Hide
          Paul King added a comment -

          If anyone is interested, here is the test case I was using:

          import java.lang.annotation.*
          import static Constants.BAR
          
          @Retention(RetentionPolicy.RUNTIME)
          @Target([ElementType.METHOD, ElementType.FIELD])
          @interface Anno {
              String value() default ""
              String[] array() default []
          }
          
          class Constants {
              static final String FOO = "foo"
              static final String BAR = "bar"
          }
          
          class ClassWithAnnotationUsingConstant {
              @Anno(array = [Constants.FOO, BAR, org.objectweb.asm.Opcodes.INVOKEDYNAMIC_OWNER])
              public annotatedField
          }
          
          assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedField').annotations[0].value() == ['foo', 'bar', "java/lang/dyn/Dynamic"]
          

          Making use of InvokeDynamic parts of ASM 3.2 (locally on my machine) already!

          Show
          Paul King added a comment - If anyone is interested, here is the test case I was using: import java.lang.annotation.* import static Constants.BAR @Retention(RetentionPolicy.RUNTIME) @Target([ElementType.METHOD, ElementType.FIELD]) @ interface Anno { String value() default "" String [] array() default [] } class Constants { static final String FOO = "foo" static final String BAR = "bar" } class ClassWithAnnotationUsingConstant { @Anno(array = [Constants.FOO, BAR, org.objectweb.asm.Opcodes.INVOKEDYNAMIC_OWNER]) public annotatedField } assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedField').annotations[0].value() == ['foo', 'bar', "java/lang/dyn/Dynamic" ] Making use of InvokeDynamic parts of ASM 3.2 (locally on my machine) already!
          Hide
          Paul King added a comment -

          fix and test applied to trunk (pending merge with 1_6_X branch but I will wait a day to let dust settle and make sure no catastrophes)

          Show
          Paul King added a comment - fix and test applied to trunk (pending merge with 1_6_X branch but I will wait a day to let dust settle and make sure no catastrophes)
          Hide
          blackdrag blackdrag added a comment -

          But Paul... am I right assuming that

          import java.lang.annotation.*
          import static Constants.BAR
          
          @Retention(RetentionPolicy.RUNTIME)
          @Target([ElementType.METHOD, ElementType.FIELD])
          @interface Anno {
              int value() default -1
          }
          
          class ClassWithAnnotationUsingConstant {
              @Anno(value = Integer.MAX_VALUE)
              public annotatedField
          }
          
          assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedField').annotations[0].value() == Integer.MAX_VALUE
          

          will not work? IMHO the solution you provided looks for a ConstantExpression and if that is there the value can be used. If not, then it is a problem. Assume that in your example Constants is precompiled and you get exactly the same problem, even if it is a groovy class. The solution you gave works only with not precompiled classes or will have unwanted side effects. using Annotation constants is a different thing, because here the constant can be read without executing the static initializer. Because of that for example @Target([ElementType.METHOD, ElementType.FIELD]) works in Groovy.

          But I am against a solution that works sometimes under some special occurrences only. So I am also against this fix.

          Show
          blackdrag blackdrag added a comment - But Paul... am I right assuming that import java.lang.annotation.* import static Constants.BAR @Retention(RetentionPolicy.RUNTIME) @Target([ElementType.METHOD, ElementType.FIELD]) @ interface Anno { int value() default -1 } class ClassWithAnnotationUsingConstant { @Anno(value = Integer .MAX_VALUE) public annotatedField } assert ClassWithAnnotationUsingConstant.getDeclaredField('annotatedField').annotations[0].value() == Integer .MAX_VALUE will not work? IMHO the solution you provided looks for a ConstantExpression and if that is there the value can be used. If not, then it is a problem. Assume that in your example Constants is precompiled and you get exactly the same problem, even if it is a groovy class. The solution you gave works only with not precompiled classes or will have unwanted side effects. using Annotation constants is a different thing, because here the constant can be read without executing the static initializer. Because of that for example @Target( [ElementType.METHOD, ElementType.FIELD] ) works in Groovy. But I am against a solution that works sometimes under some special occurrences only. So I am also against this fix.
          Hide
          Paul King added a comment -

          The example you give (at least after removing the import to Constants which isn't around in your example) will work fine and having it work for Java constants was my main motivation but I understand your reservations.

          We can't at the moment make it work for constants in pre-compiled Groovy classes but it will for constants in pre-compiled Groovy interfaces. So, sniffing around looking for the constant expressions is a bit of a compromise trying to cater for some of the more common cases.

          Can you think of a better approach? I don't think not supporting Java cases is a nice option as existing Java libraries already have such usage patterns when used from Java.

          Show
          Paul King added a comment - The example you give (at least after removing the import to Constants which isn't around in your example) will work fine and having it work for Java constants was my main motivation but I understand your reservations. We can't at the moment make it work for constants in pre-compiled Groovy classes but it will for constants in pre-compiled Groovy interfaces. So, sniffing around looking for the constant expressions is a bit of a compromise trying to cater for some of the more common cases. Can you think of a better approach? I don't think not supporting Java cases is a nice option as existing Java libraries already have such usage patterns when used from Java.
          Hide
          blackdrag blackdrag added a comment -

          if that example is working then I missed out part of your change. Before your patch, if you asked the ClassNode of Integer for the FieldNode MAX_VALUE, you got no initialExpression, so you could not get an constant expression and no value to use... right, I missed out this part:

                          try {
                              if (type.isResolved()) {
                                  Field field = type.getTypeClass().getField(pe.getPropertyAsString());
                                  if (field != null) {
                                      return new ConstantExpression(field.get(null));
                                  }
                              }
                          } catch(Exception e) {/*ignore*/}
          

          If the class has an static initializer, then this code here will cause the static initializer to be executed, else it is impossible to get the value. And I thought we don't want this to happen. Btw, this part of the code forgets to check that the field is static, but you swallow the exception anyway... which is another part I don't like. If we execute the static initializer, then why making things even worse by swallowing any exception that initializer may throw? Then I don't understand your text about interfaces. Normal interfaces are not that much different from normal classes. They have an static initializer too, and that one will get executed as well. Only in case of an annotation it is possible to read the value directly without first executing the static initializer.

          Also it is a bit unclear to me why most of your code is in StaticImportVisitor. If I got it right, then you transform ClassNode.Field variant used by a PropertyExpression, even if that is outside of an annotation. If we are going to add such dangerous code, that executes static initializers, then we should not make matters worse and execute it for parts we don't even need... that is unless I misread the code and it is really limited to annotations... If it is limited to annotations, then it should be in an annotations handling visitor or a new visitor. It is no good to just enlarge the existing visitors, especially not the big ones. Your argument that you cannot do it in ExtendedVerifier is one I cannot follow. Sure, there the values are pulled out, but what is so bad for this? Why not change AnnotationVisitor to do exactly that? It is not like the constant will have any value for us in other parts at the moment.

          As for precompiled Groovy classes. I don't folow that argument. The code you added will execute the static initializer, thus it will not make any difference between precompiled Groovy code or Java code, regardless of being a class or interface. The only case we can safely support is groovy code we currently compile, which is too limited.

          Can I think of a better approach that works in all cases? No. We could try reading the class in bytecode and extract this information from there, but that is lot more complicated and will not work if we don't have the bytecode.. which at runtime is a common case.

          Show
          blackdrag blackdrag added a comment - if that example is working then I missed out part of your change. Before your patch, if you asked the ClassNode of Integer for the FieldNode MAX_VALUE, you got no initialExpression, so you could not get an constant expression and no value to use... right, I missed out this part: try { if (type.isResolved()) { Field field = type.getTypeClass().getField(pe.getPropertyAsString()); if (field != null ) { return new ConstantExpression(field.get( null )); } } } catch (Exception e) {/*ignore*/} If the class has an static initializer, then this code here will cause the static initializer to be executed, else it is impossible to get the value. And I thought we don't want this to happen. Btw, this part of the code forgets to check that the field is static, but you swallow the exception anyway... which is another part I don't like. If we execute the static initializer, then why making things even worse by swallowing any exception that initializer may throw? Then I don't understand your text about interfaces. Normal interfaces are not that much different from normal classes. They have an static initializer too, and that one will get executed as well. Only in case of an annotation it is possible to read the value directly without first executing the static initializer. Also it is a bit unclear to me why most of your code is in StaticImportVisitor. If I got it right, then you transform ClassNode.Field variant used by a PropertyExpression, even if that is outside of an annotation. If we are going to add such dangerous code, that executes static initializers, then we should not make matters worse and execute it for parts we don't even need... that is unless I misread the code and it is really limited to annotations... If it is limited to annotations, then it should be in an annotations handling visitor or a new visitor. It is no good to just enlarge the existing visitors, especially not the big ones. Your argument that you cannot do it in ExtendedVerifier is one I cannot follow. Sure, there the values are pulled out, but what is so bad for this? Why not change AnnotationVisitor to do exactly that? It is not like the constant will have any value for us in other parts at the moment. As for precompiled Groovy classes. I don't folow that argument. The code you added will execute the static initializer, thus it will not make any difference between precompiled Groovy code or Java code, regardless of being a class or interface. The only case we can safely support is groovy code we currently compile, which is too limited. Can I think of a better approach that works in all cases? No. We could try reading the class in bytecode and extract this information from there, but that is lot more complicated and will not work if we don't have the bytecode.. which at runtime is a common case.
          Hide
          Paul King added a comment -

          The ClassNode.Field variant is only looked at when inside annotations unless I made a mistake in the code that checks that. So, yes we are executing the static initializer but for what should be only a very small handful of classes. I'll have a think about how to move the code. The reason I put it where I did was because I couldn't get it to work in ExtendedVerifier. I might ask for your assistance later. Do you know what the javac compiler does here? Do you know if it has equivalent code that would cause static initializers to be executed?

          Show
          Paul King added a comment - The ClassNode.Field variant is only looked at when inside annotations unless I made a mistake in the code that checks that. So, yes we are executing the static initializer but for what should be only a very small handful of classes. I'll have a think about how to move the code. The reason I put it where I did was because I couldn't get it to work in ExtendedVerifier. I might ask for your assistance later. Do you know what the javac compiler does here? Do you know if it has equivalent code that would cause static initializers to be executed?
          Hide
          blackdrag blackdrag added a comment -

          javac is a different problem domain, since javac has always the byte or the source code. javac must inline the constant because no other option is allowed. that means javac will read the bytecode, extract the value and then... just found an interesting corner case for javac:

          class Constants {
            final static String FOO;
            static {
              FOO = "foo";
            }
          }
          
          class ClassWithAnnotationUsingConstant {
              @Anno(value=Constants.FOO)
              public Object annotatedField;
          }
          
          

          This code does not compile. And indeed the inline version is represented different in bytecode. Now that opens a new issue for Groovy. To allow this usage pattern Groovy has to do it the same way, meaning inline. If not then it will make problems even from Java. Sadly reflection does not provide any way to go around this issue. There is only one way, and that will get the static initializer being executed.

          Show
          blackdrag blackdrag added a comment - javac is a different problem domain, since javac has always the byte or the source code. javac must inline the constant because no other option is allowed. that means javac will read the bytecode, extract the value and then... just found an interesting corner case for javac: class Constants { final static String FOO; static { FOO = "foo" ; } } class ClassWithAnnotationUsingConstant { @Anno(value=Constants.FOO) public Object annotatedField; } This code does not compile. And indeed the inline version is represented different in bytecode. Now that opens a new issue for Groovy. To allow this usage pattern Groovy has to do it the same way, meaning inline. If not then it will make problems even from Java. Sadly reflection does not provide any way to go around this issue. There is only one way, and that will get the static initializer being executed.
          Hide
          Peter Niederwieser added a comment - - edited

          Have you come to a conclusion on how to solve this issue? We frequently use String constants in annotations, and it's very awkward that Groovy forces us to inline them.

          Show
          Peter Niederwieser added a comment - - edited Have you come to a conclusion on how to solve this issue? We frequently use String constants in annotations, and it's very awkward that Groovy forces us to inline them.
          Hide
          Damian Nowak added a comment -

          I test EL evaluator on my bean in Spock. Because of this bug I am not able to compile the code.

          class VisibleAndEditableEntity {
          
              public static final String PASSING_CONDITION = "#{e.age > 20}";
              public static final String FAILING_CONDITION = "#{e.age < 20}";
          
              int age = 25;
          
              @Visible(PASSING_CONDITION)
              Object visiblePassed;
          
              @Visible(FAILING_CONDITION)
              Object visibleFailed;
          
              @Hidden(PASSING_CONDITION)
              Object hiddenPassed;
          
              @Hidden(FAILING_CONDITION)
              Object hiddenFailed;
          
          }
          
          AnnotationReaderFieldVisibilityGroovyTest.groovy: 87: expected 'PASSING_CONDITION' to be an inline constant of type java.lang.String not a field expression in @za.co.fnb.csip.jastin.annotation.Visible
          @ line 87, column 14.
          @Visible(PASSING_CONDITION)
          
          (and so on)
          

          That's unacceptable. When I explicitly say public static final String, @Visible(PASSING_CONDITION) should be allowed!

          Show
          Damian Nowak added a comment - I test EL evaluator on my bean in Spock. Because of this bug I am not able to compile the code. class VisibleAndEditableEntity { public static final String PASSING_CONDITION = "#{e.age > 20}"; public static final String FAILING_CONDITION = "#{e.age < 20}"; int age = 25; @Visible(PASSING_CONDITION) Object visiblePassed; @Visible(FAILING_CONDITION) Object visibleFailed; @Hidden(PASSING_CONDITION) Object hiddenPassed; @Hidden(FAILING_CONDITION) Object hiddenFailed; } AnnotationReaderFieldVisibilityGroovyTest.groovy: 87: expected 'PASSING_CONDITION' to be an inline constant of type java.lang.String not a field expression in @za.co.fnb.csip.jastin.annotation.Visible @ line 87, column 14. @Visible(PASSING_CONDITION) (and so on) That's unacceptable. When I explicitly say public static final String , @Visible(PASSING_CONDITION) should be allowed!
          Hide
          Pascal Schumacher added a comment -
          import java.lang.annotation.*
          
          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.TYPE)
          @interface MyAnnotation {
              String[] groups() default []
          }
          
          class Base {
              static final String CONST = 'bing'
              def run() { getClass().annotations[0].groups() }
          }
          
          @MyAnnotation(groups = 'foo')
          class Child1 extends Base {}
          
          @MyAnnotation(groups = ['bar', 'baz', Base.CONST]) 
          class Child2 extends Base {}
          
          assert new Child1().run() == ['foo']
          assert new Child2().run() == ['bar', 'baz', 'bing']

          does work with Groovy 2.1.7, so I guess this was fixed?

          Show
          Pascal Schumacher added a comment - import java.lang.annotation.* @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @ interface MyAnnotation { String [] groups() default [] } class Base { static final String CONST = 'bing' def run() { getClass().annotations[0].groups() } } @MyAnnotation(groups = 'foo') class Child1 extends Base {} @MyAnnotation(groups = ['bar', 'baz', Base.CONST]) class Child2 extends Base {} assert new Child1().run() == ['foo'] assert new Child2().run() == ['bar', 'baz', 'bing'] does work with Groovy 2.1.7, so I guess this was fixed?
          Hide
          Paul King added a comment -

          Pascal, it works if you are in the same source unit. I think you'll find that if you place a constant in a separate file, e.g.:

          class Constants {
              static final String OTHER = 'fizz'
          }
          

          and reference that in your script above we still have a problem. I think though, we might have re-jigged how such constants are represented in our files and perhaps some rework might now be possible.

          Show
          Paul King added a comment - Pascal, it works if you are in the same source unit. I think you'll find that if you place a constant in a separate file, e.g.: class Constants { static final String OTHER = 'fizz' } and reference that in your script above we still have a problem. I think though, we might have re-jigged how such constants are represented in our files and perhaps some rework might now be possible.
          Hide
          Edward Povazan added a comment -

          I've found in Grails that if I specify "public", this works. e.g.

          public static final String ADMIN = "ROLE_ADMIN"
          

          compared to

          static final String ADMIN = "ROLE_ADMIN"
          
          Show
          Edward Povazan added a comment - I've found in Grails that if I specify "public", this works. e.g. public static final String ADMIN = "ROLE_ADMIN" compared to static final String ADMIN = "ROLE_ADMIN"
          Hide
          Christopher Smith added a comment -

          Is there any progress on this issue? I can confirm that the generated class file for the Groovy class with the constant properly presents the string as a java.lang.String constant, but the compiler still generates a bogus error trying to use it in an annotation parameter from outside the class.

          Show
          Christopher Smith added a comment - Is there any progress on this issue? I can confirm that the generated class file for the Groovy class with the constant properly presents the string as a java.lang.String constant, but the compiler still generates a bogus error trying to use it in an annotation parameter from outside the class.
          Hide
          K Smith added a comment - - edited

          Ugh. Just hit this myself in grails (2.3.7) trying to pass a static final Class array to a @Mock() annotation. 5+ years and no resolution? Kinda silly to have to inline things.

          (Threw me for a loop because IntelliJ doesn't complain, nor does IntelliJ "make" complain, but a "rebuild" does give the error: "@Mock annotation expects a class or a list of classes to mock")

          Going to try the 'public' based on Edward's comment. 'public' forces it to be a field instead of a property, correct?

          UPDATE: adding 'public' to my 'static final Class[]' made no difference. Same error.

          Show
          K Smith added a comment - - edited Ugh. Just hit this myself in grails (2.3.7) trying to pass a static final Class array to a @Mock() annotation. 5+ years and no resolution? Kinda silly to have to inline things. (Threw me for a loop because IntelliJ doesn't complain, nor does IntelliJ "make" complain, but a "rebuild" does give the error: "@Mock annotation expects a class or a list of classes to mock") Going to try the 'public' based on Edward's comment. 'public' forces it to be a field instead of a property, correct? UPDATE: adding 'public' to my 'static final Class[]' made no difference. Same error.

            People

            • Assignee:
              Unassigned
              Reporter:
              Gregg Bolinger
            • Votes:
              20 Vote for this issue
              Watchers:
              21 Start watching this issue

              Dates

              • Created:
                Updated: