You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2018/11/05 23:40:25 UTC

groovy git commit: GROOVY-7975/GROOVY-3278/GROOVY-7854: improved accessing of constants for annotation attributes (closes #819)

Repository: groovy
Updated Branches:
  refs/heads/master 44b7cdc4e -> 7d2d6319f


GROOVY-7975/GROOVY-3278/GROOVY-7854: improved accessing of constants for annotation attributes (closes #819)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/7d2d6319
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/7d2d6319
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/7d2d6319

Branch: refs/heads/master
Commit: 7d2d6319f694b87fc4e646d3d22679315e1411bf
Parents: 44b7cdc
Author: Paul King <pa...@asert.com.au>
Authored: Sun Nov 4 20:10:43 2018 +1000
Committer: Paul King <pa...@asert.com.au>
Committed: Mon Nov 5 18:37:54 2018 +1000

----------------------------------------------------------------------
 .../groovy/ast/tools/ExpressionUtils.java       | 278 ++++++++++++++++++-
 .../groovy/classgen/AnnotationVisitor.java      |  45 +--
 .../org/codehaus/groovy/classgen/Verifier.java  |   7 +-
 .../control/AnnotationConstantsVisitor.java     |   6 +-
 src/test/gls/annotations/AnnotationTest.groovy  |  88 +++++-
 src/test/gls/annotations/Base3278.groovy        |   7 +-
 src/test/gls/annotations/Child2.groovy          |   2 +-
 src/test/gls/annotations/ConstAnnotation.groovy |  26 ++
 src/test/gls/annotations/MyAnnotation.groovy    |  25 --
 9 files changed, 410 insertions(+), 74 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java b/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
index c3d9737..8fab0db 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ExpressionUtils.java
@@ -18,20 +18,291 @@
  */
 package org.apache.groovy.ast.tools;
 
+import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
 import org.codehaus.groovy.ast.expr.ClassExpression;
 import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.ListExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.runtime.typehandling.NumberMath;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
+import static org.codehaus.groovy.syntax.Types.BITWISE_OR;
+import static org.codehaus.groovy.syntax.Types.BITWISE_XOR;
+import static org.codehaus.groovy.syntax.Types.DIVIDE;
+import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT;
+import static org.codehaus.groovy.syntax.Types.MINUS;
+import static org.codehaus.groovy.syntax.Types.MULTIPLY;
+import static org.codehaus.groovy.syntax.Types.PLUS;
+import static org.codehaus.groovy.syntax.Types.POWER;
+import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT;
+import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
 
 public class ExpressionUtils {
+    private static ArrayList<Integer> handledTypes = new ArrayList<Integer>();
+
     private ExpressionUtils() {
 
     }
 
-    // resolve constant-looking expressions statically (do here as gets transformed away later)
+    static {
+        handledTypes.add(PLUS);
+        handledTypes.add(MINUS);
+        handledTypes.add(MULTIPLY);
+        handledTypes.add(DIVIDE);
+        handledTypes.add(LEFT_SHIFT);
+        handledTypes.add(RIGHT_SHIFT);
+        handledTypes.add(RIGHT_SHIFT_UNSIGNED);
+        handledTypes.add(BITWISE_OR);
+        handledTypes.add(BITWISE_AND);
+        handledTypes.add(BITWISE_XOR);
+        handledTypes.add(POWER);
+    }
+
+    /**
+     * Turns expressions of the form ConstantExpression(40) + ConstantExpression(2)
+     * into the simplified ConstantExpression(42) at compile time.
+     *
+     * @param be the binary expression
+     * @param targetType the type of the result
+     * @return the transformed expression or the original if no transformation was performed
+     */
+    public static ConstantExpression transformBinaryConstantExpression(BinaryExpression be, ClassNode targetType) {
+        ClassNode wrapperType = ClassHelper.getWrapper(targetType);
+        if (isTypeOrArrayOfType(targetType, ClassHelper.STRING_TYPE, false)) {
+            if (be.getOperation().getType() == PLUS) {
+                Expression left = transformInlineConstants(be.getLeftExpression(), targetType);
+                Expression right = transformInlineConstants(be.getRightExpression(), targetType);
+                if (left instanceof ConstantExpression && right instanceof ConstantExpression) {
+                    return configure(be, new ConstantExpression((String) ((ConstantExpression) left).getValue() +
+                            ((ConstantExpression) right).getValue()));
+                }
+            }
+        } else if (isNumberOrArrayOfNumber(wrapperType, false)) {
+            int type = be.getOperation().getType();
+            if (handledTypes.contains(type)) {
+                Expression leftX = transformInlineConstants(be.getLeftExpression(), targetType);
+                Expression rightX = transformInlineConstants(be.getRightExpression(), targetType);
+                if (leftX instanceof ConstantExpression && rightX instanceof ConstantExpression) {
+                    Number left = safeNumber((ConstantExpression) leftX);
+                    Number right = safeNumber((ConstantExpression) rightX);
+                    if (left == null || right == null) return null;
+                    Number result = null;
+                    switch(type) {
+                        case PLUS:
+                            result = NumberMath.add(left, right);
+                            break;
+                        case MINUS:
+                            result = NumberMath.subtract(left, right);
+                            break;
+                        case MULTIPLY:
+                            result = NumberMath.multiply(left, right);
+                            break;
+                        case DIVIDE:
+                            result = NumberMath.divide(left, right);
+                            break;
+                        case LEFT_SHIFT:
+                            result = NumberMath.leftShift(left, right);
+                            break;
+                        case RIGHT_SHIFT:
+                            result = NumberMath.rightShift(left, right);
+                            break;
+                        case RIGHT_SHIFT_UNSIGNED:
+                            result = NumberMath.rightShiftUnsigned(left, right);
+                            break;
+                        case BITWISE_AND:
+                            result = NumberMath.and(left, right);
+                            break;
+                        case BITWISE_OR:
+                            result = NumberMath.or(left, right);
+                            break;
+                        case BITWISE_XOR:
+                            result = NumberMath.xor(left, right);
+                            break;
+                        case POWER:
+                            result = DefaultGroovyMethods.power(left, right);
+                            break;
+                    }
+                    if (result != null) {
+                        if (ClassHelper.Byte_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.byteValue(), true));
+                        }
+                        if (ClassHelper.Short_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.shortValue(), true));
+                        }
+                        if (ClassHelper.Long_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.longValue(), true));
+                        }
+                        if (ClassHelper.Integer_TYPE.equals(wrapperType) || ClassHelper.Character_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.intValue(), true));
+                        }
+                        if (ClassHelper.Float_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.floatValue(), true));
+                        }
+                        if (ClassHelper.Double_TYPE.equals(wrapperType)) {
+                            return configure(be, new ConstantExpression(result.doubleValue(), true));
+                        }
+                        return configure(be, new ConstantExpression(result, true));
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private static Number safeNumber(ConstantExpression constX) {
+        Object value = constX.getValue();
+        if (value instanceof Number) return (Number) value;
+        return null;
+    }
+
+    private static ConstantExpression configure(Expression origX, ConstantExpression newX) {
+        newX.setSourcePosition(origX);
+        return newX;
+    }
+
+    /**
+     * Determine if a type matches another type (or array thereof).
+     *
+     * @param targetType the candidate type
+     * @param type the type we are checking against
+     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
+     * @return true if the type equals the targetType or array thereof
+     */
+    public static boolean isTypeOrArrayOfType(ClassNode targetType, ClassNode type, boolean recurse) {
+        if (targetType == null) return false;
+        return type.equals(targetType) ||
+                (targetType.isArray() && recurse
+                ? isTypeOrArrayOfType(targetType.getComponentType(), type, recurse)
+                : type.equals(targetType.getComponentType()));
+    }
+
+    /**
+     * Determine if a type is derived from Number (or array thereof).
+     *
+     * @param targetType the candidate type
+     * @param recurse true if we can have multi-dimension arrays; should be false for annotation member types
+     * @return true if the type equals the targetType or array thereof
+     */
+    public static boolean isNumberOrArrayOfNumber(ClassNode targetType, boolean recurse) {
+        if (targetType == null) return false;
+        return targetType.isDerivedFrom(ClassHelper.Number_TYPE) ||
+                (targetType.isArray() && recurse
+                ? isNumberOrArrayOfNumber(targetType.getComponentType(), recurse)
+                : targetType.isArray() && targetType.getComponentType().isDerivedFrom(ClassHelper.Number_TYPE));
+    }
+
+    /**
+     * Converts simple expressions of constants into pre-evaluated simple constants.
+     * Handles:
+     * <ul>
+     *     <li>Property expressions - referencing constants</li>
+     *     <li>Simple binary expressions - String concatenation and numeric +, -, /, *</li>
+     *     <li>List expressions - list of constants</li>
+     *     <li>Variable expressions - referencing constants</li>
+     * </ul>
+     * @param exp the original expression
+     * @param attrType the type that the final constant should be
+     * @return the transformed type or the original if no transformation was possible
+     */
+    public static Expression transformInlineConstants(final Expression exp, final ClassNode attrType) {
+        if (exp instanceof PropertyExpression) {
+            PropertyExpression pe = (PropertyExpression) exp;
+            if (pe.getObjectExpression() instanceof ClassExpression) {
+                ClassExpression ce = (ClassExpression) pe.getObjectExpression();
+                ClassNode type = ce.getType();
+                if (type.isEnum() || !(type.isResolved() || type.isPrimaryClassNode()))
+                    return exp;
+
+                if (type.isPrimaryClassNode()) {
+                    FieldNode fn = type.redirect().getField(pe.getPropertyAsString());
+                    if (fn != null && fn.isStatic() && fn.isFinal()) {
+                        Expression ce2 = transformInlineConstants(fn.getInitialValueExpression(), attrType);
+                        if (ce2 != null) {
+                            return ce2;
+                        }
+                    }
+                } else {
+                    try {
+                        Field field = type.redirect().getTypeClass().getField(pe.getPropertyAsString());
+                        if (field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
+                            ConstantExpression ce3 = new ConstantExpression(field.get(null), true);
+                            ce3.setSourcePosition(exp);
+                            return ce3;
+                        }
+                    } catch(Exception e) {
+                        // ignore, leave property expression in place and we'll report later
+                    }
+                }
+            }
+        } else if (exp instanceof BinaryExpression) {
+            ConstantExpression ce = transformBinaryConstantExpression((BinaryExpression) exp, attrType);
+            if (ce != null) {
+                return ce;
+            }
+        } else if (exp instanceof VariableExpression) {
+            VariableExpression ve = (VariableExpression) exp;
+            if (ve.getAccessedVariable() instanceof FieldNode) {
+                FieldNode fn = (FieldNode) ve.getAccessedVariable();
+                if (fn.isStatic() && fn.isFinal()) {
+                    Expression ce = transformInlineConstants(fn.getInitialValueExpression(), attrType);
+                    if (ce != null) {
+                        return ce;
+                    }
+                }
+            }
+        } else if (exp instanceof ListExpression) {
+            return transformListOfConstants((ListExpression) exp, attrType);
+        }
+        return exp;
+    }
+
+    /**
+     * Given a list of constants, transform each item in the list.
+     *
+     * @param origList the list to transform
+     * @param attrType the target type
+     * @return the transformed list or the original if nothing was changed
+     */
+    public static Expression transformListOfConstants(ListExpression origList, ClassNode attrType) {
+        ListExpression newList = new ListExpression();
+        boolean changed = false;
+        for (Expression e : origList.getExpressions()) {
+            try {
+                Expression transformed = transformInlineConstants(e, attrType);
+                newList.addExpression(transformed);
+                if (transformed != e) changed = true;
+            } catch(Exception ignored) {
+                newList.addExpression(e);
+            }
+        }
+        if (changed) {
+            newList.setSourcePosition(origList);
+            return newList;
+        }
+        return origList;
+    }
+
+    /**
+     * The attribute values of annotations must be primitive or String constants.
+     * In various places, such constants can be seen during type resolution but won't be
+     * readily accessible in later phases, e.g. they might be embedded into constructor code.
+     * This method transforms constants that would appear in annotations early so they aren't lost.
+     * Subsequent processing determines whether they are valid, this method simply retains
+     * the constant value as a constant expression.
+     *
+     * @param exp the original expression
+     * @return the converted expression
+     */
     public static Expression transformInlineConstants(final Expression exp) {
         if (exp instanceof PropertyExpression) {
             PropertyExpression pe = (PropertyExpression) exp;
@@ -42,6 +313,11 @@ public class ExpressionUtils {
                 Expression constant = findConstant(ClassNodeUtils.getField(type, pe.getPropertyAsString()));
                 if (constant != null) return constant;
             }
+        } else if (exp instanceof BinaryExpression) {
+            BinaryExpression be = (BinaryExpression) exp;
+            be.setLeftExpression(transformInlineConstants(be.getLeftExpression()));
+            be.setRightExpression(transformInlineConstants(be.getRightExpression()));
+            return be;
         } else if (exp instanceof ListExpression) {
             ListExpression origList = (ListExpression) exp;
             ListExpression newList = new ListExpression();

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
index 103d5e1..46c471e 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AnnotationVisitor.java
@@ -39,11 +39,11 @@ import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
 import org.codehaus.groovy.syntax.SyntaxException;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.List;
 import java.util.Map;
 
+import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
+
 /**
  * An Annotation visitor responsible for:
  * <ul>
@@ -89,9 +89,9 @@ public class AnnotationVisitor {
         Map<String, Expression> attributes = node.getMembers();
         for (Map.Entry<String, Expression> entry : attributes.entrySet()) {
             String attrName = entry.getKey();
-            Expression attrExpr = transformInlineConstants(entry.getValue());
-            entry.setValue(attrExpr);
             ClassNode attrType = getAttributeType(node, attrName);
+            Expression attrExpr = transformInlineConstants(entry.getValue(), attrType);
+            entry.setValue(attrExpr);
             visitExpression(attrName, attrExpr, attrType);
         }
         VMPluginFactory.getPlugin().configureAnnotation(node);
@@ -132,35 +132,6 @@ public class AnnotationVisitor {
         return true;
     }
 
-    private Expression transformInlineConstants(Expression exp) {
-        if (exp instanceof PropertyExpression) {
-            PropertyExpression pe = (PropertyExpression) exp;
-            if (pe.getObjectExpression() instanceof ClassExpression) {
-                ClassExpression ce = (ClassExpression) pe.getObjectExpression();
-                ClassNode type = ce.getType();
-                if (type.isEnum() || !type.isResolved())
-                    return exp;
-
-                try {
-                    Field field = type.redirect().getTypeClass().getField(pe.getPropertyAsString());
-                    if (field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers())) {
-                        return new ConstantExpression(field.get(null));
-                    }
-                } catch(Exception e) {
-                    // ignore, leave property expression in place and we'll report later
-                }
-            }
-        } else if (exp instanceof ListExpression) {
-            ListExpression le = (ListExpression) exp;
-            ListExpression result = new ListExpression();
-            for (Expression e : le.getExpressions()) {
-                result.addExpression(transformInlineConstants(e));
-            }
-            return result;
-        }
-        return exp;
-    }
-
     private boolean checkIfMandatoryAnnotationValuesPassed(AnnotationNode node) {
         boolean ok = true;
         Map attributes = node.getMembers();
@@ -250,8 +221,12 @@ public class AnnotationVisitor {
     }
 
     private ConstantExpression getConstantExpression(Expression exp, ClassNode attrType) {
-        if (exp instanceof ConstantExpression) {
-            return (ConstantExpression) exp;
+        Expression result = exp;
+        if (!(result instanceof ConstantExpression)) {
+            result = transformInlineConstants(result, attrType);
+        }
+        if (result instanceof ConstantExpression) {
+            return (ConstantExpression) result;
         }
         String base = "Expected '" + exp.getText() + "' to be an inline constant of type " + attrType.getName();
         if (exp instanceof PropertyExpression) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/main/java/org/codehaus/groovy/classgen/Verifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
index 439eefb..c42fcc8 100644
--- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java
+++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java
@@ -89,6 +89,7 @@ import static java.lang.reflect.Modifier.isPrivate;
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
 import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
+import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptorWithoutReturnType;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
@@ -1142,10 +1143,12 @@ public class Verifier implements GroovyClassVisitor, Opcodes {
                 // GROOVY-3311: pre-defined constants added by groovy compiler for numbers/characters should be
                 // initialized first so that code dependent on it does not see their values as empty
                 Expression initialValueExpression = fieldNode.getInitialValueExpression();
-                if (initialValueExpression instanceof ConstantExpression) {
-                    ConstantExpression cexp = (ConstantExpression) initialValueExpression;
+                Expression transformed = transformInlineConstants(initialValueExpression, fieldNode.getType());
+                if (transformed instanceof ConstantExpression) {
+                    ConstantExpression cexp = (ConstantExpression) transformed;
                     cexp = transformToPrimitiveConstantIfPossible(cexp);
                     if (fieldNode.isFinal() && ClassHelper.isStaticConstantInitializerType(cexp.getType()) && cexp.getType().equals(fieldNode.getType())) {
+                        fieldNode.setInitialValueExpression(transformed);
                         return; // GROOVY-5150: primitive type constants will be initialized directly
                     }
                     staticList.add(0, statement);

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java b/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java
index b1610df..a3785f8 100644
--- a/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java
+++ b/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java
@@ -32,10 +32,10 @@ import org.codehaus.groovy.classgen.Verifier;
 
 import java.math.BigDecimal;
 
+import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
+
 /**
  * Visitor to resolve constants in annotation definitions.
- *
- * @author Paul King
  */
 public class AnnotationConstantsVisitor extends ClassCodeVisitorSupport {
     private SourceUnit source;
@@ -89,7 +89,7 @@ public class AnnotationConstantsVisitor extends ClassCodeVisitorSupport {
                 return castee;
             }
         }
-        return val;
+        return transformInlineConstants(val, returnType);
     }
 
     private static Expression revertType(Expression val, ClassNode returnWrapperType) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/test/gls/annotations/AnnotationTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/AnnotationTest.groovy b/src/test/gls/annotations/AnnotationTest.groovy
index c3461ed..c79b290 100644
--- a/src/test/gls/annotations/AnnotationTest.groovy
+++ b/src/test/gls/annotations/AnnotationTest.groovy
@@ -755,20 +755,96 @@ class AnnotationTest extends CompilableTestSupport {
         '''
     }
 
+    void testVariableExpressionsReferencingConstantsSeenForAnnotationAttributes() {
+        shouldCompile '''
+            class C {
+                public static final String VALUE = 'rawtypes'
+                @SuppressWarnings(VALUE)
+                def method() { }
+            }
+        '''
+    }
+
+    void testSimpleBinaryExpressions() {
+        assertScript '''
+            import java.lang.annotation.*
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface Pattern {
+                String regexp()
+            }
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface LimitedDouble {
+                double max() default Double.MAX_VALUE
+                double min() default Double.MIN_VALUE
+                double zero() default (1.0 - 1.0d) * 1L
+            }
+
+            @Retention(RetentionPolicy.RUNTIME)
+            @Target(ElementType.FIELD)
+            @interface IntegerConst {
+                int kilo() default 0b1 << 10
+                int two() default 64 >> 5
+                int nine() default 0b1100 ^ 0b101
+                int answer() default 42
+            }
+
+            class MyClass {
+                private static interface Constants {
+                    static final String CONST = 'foo' + 'bar'
+                }
+
+                @Pattern(regexp=Constants.CONST)
+                String myString
+
+                @LimitedDouble(min=0.0d, max=50.0d * 2)
+                double myDouble
+
+                @IntegerConst(answer=(5 ** 2) * (2 << 1))
+                int myInteger
+            }
+
+            assert MyClass.getDeclaredField('myString').annotations[0].regexp() == 'foobar'
+
+            MyClass.getDeclaredField('myDouble').annotations[0].with {
+                assert max() == 100.0d
+                assert min() == 0.0d
+                assert zero() == 0.0d
+            }
+
+            MyClass.getDeclaredField('myInteger').annotations[0].with {
+                assert kilo() == 1024
+                assert two() == 2
+                assert nine() == 9
+                assert answer() == 100
+            }
+        '''
+    }
+
     void testAnnotationAttributeConstantFromPrecompiledGroovyClass() {
         // GROOVY-3278
         assertScript '''
-            @MyAnnotation(groups = 42)
+            @ConstAnnotation(ints = 42)
             class Child1 extends Base3278 {}
+            
+            class OtherConstants {
+                static final Integer CONST3 = 3278
+            }
 
-            @MyAnnotation(groups = [-1, Base3278.CONST, Integer.MIN_VALUE])
+            @ConstAnnotation(ints = [-1, Base3278.CONST, Base3278.CONST1, Base3278.CONST2, OtherConstants.CONST3, Integer.MIN_VALUE],
+                          strings = ['foo', Base3278.CONST4, Base3278.CONST5, Base3278.CONST5 + 'bing'])
             class Child3 extends Base3278 {}
 
-            assert new Child1().run() == [42]
-            assert new Child2().run() == [2147483647]
-            assert new Child3().run() == [-1, 3278, -2147483648]
+            assert new Child1().ints() == [42]
+            assert new Child2().ints() == [2147483647]
+            new Child3().with {
+                assert ints() == [-1, 3278, 2048, 2070, 3278, -2147483648]
+                assert strings() == ['foo', 'foobar', 'foobarbaz', 'foobarbazbing']
+            }
         '''
-
     }
 
     void testAnnotationWithRepeatableSupportedPrecompiledJava() {

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/test/gls/annotations/Base3278.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/Base3278.groovy b/src/test/gls/annotations/Base3278.groovy
index 05da7fa..a3ecd3b 100644
--- a/src/test/gls/annotations/Base3278.groovy
+++ b/src/test/gls/annotations/Base3278.groovy
@@ -18,5 +18,10 @@
  */
 class Base3278 {
     static final int CONST = 3278
-    def run() { getClass().annotations[0].groups() }
+    static final int CONST1 = 1024 * 2
+    static final int CONST2 = CONST1 + 22
+    static final String CONST4 = 'foobar'
+    static final String CONST5 = CONST4 + 'baz'
+    def ints() { getClass().annotations[0].ints() }
+    def strings() { getClass().annotations[0].strings() }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/test/gls/annotations/Child2.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/Child2.groovy b/src/test/gls/annotations/Child2.groovy
index 16fe19a..afa762d 100644
--- a/src/test/gls/annotations/Child2.groovy
+++ b/src/test/gls/annotations/Child2.groovy
@@ -16,5 +16,5 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-@MyAnnotation(groups = Integer.MAX_VALUE)
+@ConstAnnotation(ints = Integer.MAX_VALUE)
 class Child2 extends Base3278 {}

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/test/gls/annotations/ConstAnnotation.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/ConstAnnotation.groovy b/src/test/gls/annotations/ConstAnnotation.groovy
new file mode 100644
index 0000000..e1e780f
--- /dev/null
+++ b/src/test/gls/annotations/ConstAnnotation.groovy
@@ -0,0 +1,26 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+import java.lang.annotation.*
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@interface ConstAnnotation {
+    int[] ints() default []
+    String[] strings() default []
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/7d2d6319/src/test/gls/annotations/MyAnnotation.groovy
----------------------------------------------------------------------
diff --git a/src/test/gls/annotations/MyAnnotation.groovy b/src/test/gls/annotations/MyAnnotation.groovy
deleted file mode 100644
index 5a4d0f3..0000000
--- a/src/test/gls/annotations/MyAnnotation.groovy
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-import java.lang.annotation.*
-
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-@interface MyAnnotation {
-    int[] groups() default []
-}