You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2023/08/25 15:17:57 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10756: fix erasure (like `ResolveVisitor#resolveGenericsHeader`)

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new 1815cdc8ea GROOVY-10756: fix erasure (like `ResolveVisitor#resolveGenericsHeader`)
1815cdc8ea is described below

commit 1815cdc8ea2d42e34470ad98c08198cdbd8bcbd8
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Aug 25 08:22:33 2023 -0500

    GROOVY-10756: fix erasure (like `ResolveVisitor#resolveGenericsHeader`)
    
    4_0_X backport
---
 .../groovy/ast/decompiled/TypeSignatureParser.java |  10 +-
 .../org/codehaus/groovy/vmplugin/v8/Java8.java     | 296 ++++++++++-----------
 .../stc/ClosureParamTypeInferenceSTCTest.groovy    |   5 +
 .../groovy/ast/decompiled/AsmDecompilerTest.groovy | 215 ++++++++-------
 4 files changed, 275 insertions(+), 251 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/decompiled/TypeSignatureParser.java b/src/main/java/org/codehaus/groovy/ast/decompiled/TypeSignatureParser.java
index fd9b38e9c3..c623f61b79 100644
--- a/src/main/java/org/codehaus/groovy/ast/decompiled/TypeSignatureParser.java
+++ b/src/main/java/org/codehaus/groovy/ast/decompiled/TypeSignatureParser.java
@@ -121,11 +121,11 @@ abstract class TypeSignatureParser extends SignatureVisitor {
                 try {
                     // GROOVY-10153, GROOVY-10651, GROOVY-10671: "?" or "? super T" (see ResolveVisitor#resolveWildcardBounding)
                     for (int i = 0, n = arguments.size(); i < n; i += 1) { GenericsType argument = arguments.get(i);
-                    if (!argument.isWildcard() || argument.getUpperBounds() != null) continue; //
-                    ClassNode[] implicitBounds = baseType.getGenericsTypes()[i].getUpperBounds();
-                    if (implicitBounds != null && !ClassHelper.isObjectType(implicitBounds[0])) {
-                        argument.getType().setRedirect(implicitBounds[0]); // bound is not Object
-                    }
+                        if (!argument.isWildcard() || argument.getUpperBounds() != null) continue; //
+                        ClassNode[] implicitBounds = baseType.getGenericsTypes()[i].getUpperBounds();
+                        if (implicitBounds != null && !ClassHelper.isObjectType(implicitBounds[0])) {
+                            argument.getType().setRedirect(implicitBounds[0]); // bound is not Object
+                        }
                     }
                 } catch (StackOverflowError ignore) {
                     // TODO: self-referential type parameter
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
index a02c2de94f..9152f6011a 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/Java8.java
@@ -61,6 +61,7 @@ import java.lang.reflect.Field;
 import java.lang.reflect.GenericArrayType;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.MalformedParameterizedTypeException;
+import java.lang.reflect.Member;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.ReflectPermission;
@@ -70,7 +71,6 @@ import java.lang.reflect.WildcardType;
 import java.security.Permission;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Optional;
 import java.util.stream.Collectors;
 
 /**
@@ -84,14 +84,16 @@ public class Java8 implements VMPlugin {
     private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
     private static final Permission ACCESS_PERMISSION = new ReflectPermission("suppressAccessChecks");
 
-    public static GenericsType configureTypeVariableDefinition(final ClassNode base, final ClassNode[] cBounds) {
+    public static GenericsType configureTypeVariableDefinition(final ClassNode base, final ClassNode[] bounds) {
         ClassNode redirect = base.redirect();
         base.setRedirect(null);
         GenericsType gt;
-        if (cBounds == null || cBounds.length == 0) {
+        if (bounds == null || bounds.length == 0) {
             gt = new GenericsType(base);
         } else {
-            gt = new GenericsType(base, cBounds, null);
+            // GROOVY-10756: fix erasure -- ResolveVisitor#resolveGenericsHeader
+            if (!ClassHelper.isObjectType(bounds[0])) redirect = bounds[0];
+            gt = new GenericsType(base, bounds, null);
             gt.setName(base.getName());
             gt.setPlaceholder(true);
         }
@@ -99,46 +101,42 @@ public class Java8 implements VMPlugin {
         return gt;
     }
 
-    private static ClassNode configureClass(final Class<?> c) {
-        if (c.isPrimitive()) {
-            return ClassHelper.make(c);
-        } else {
-            return ClassHelper.makeWithoutCaching(c, false);
-        }
-    }
-
     public static ClassNode configureTypeVariableReference(final String name) {
         ClassNode cn = ClassHelper.makeWithoutCaching(name);
         cn.setGenericsPlaceHolder(true);
         ClassNode cn2 = ClassHelper.makeWithoutCaching(name);
         cn2.setGenericsPlaceHolder(true);
-        GenericsType[] gts = new GenericsType[]{new GenericsType(cn2)};
-        cn.setGenericsTypes(gts);
+
+        cn.setGenericsTypes(new GenericsType[]{new GenericsType(cn2)});
         cn.setRedirect(ClassHelper.OBJECT_TYPE);
         return cn;
     }
 
+    private static ClassNode configureClass(final Class<?> c) {
+        if (c.isPrimitive()) {
+            return ClassHelper.make(c);
+        } else {
+            return ClassHelper.makeWithoutCaching(c, false);
+        }
+    }
+
     private static void setRetentionPolicy(final RetentionPolicy value, final AnnotationNode node) {
         switch (value) {
-            case RUNTIME:
-                node.setRuntimeRetention(true);
-                break;
-            case SOURCE:
-                node.setSourceRetention(true);
-                break;
-            case CLASS:
-                node.setClassRetention(true);
-                break;
-            default:
-                throw new GroovyBugError("unsupported Retention " + value);
+          case RUNTIME:
+            node.setRuntimeRetention(true);
+            break;
+          case SOURCE:
+            node.setSourceRetention(true);
+            break;
+          case CLASS:
+            node.setClassRetention(true);
+            break;
+          default:
+            throw new GroovyBugError("unsupported Retention " + value);
         }
     }
 
-    private static void setMethodDefaultValue(final MethodNode mn, final Method m) {
-        ConstantExpression cExp = new ConstantExpression(m.getDefaultValue());
-        mn.setCode(new ReturnStatement(cExp));
-        mn.setAnnotationDefault(true);
-    }
+    //--------------------------------------------------------------------------
 
     @Override
     public Class<?>[] getPluginDefaultGroovyMethods() {
@@ -161,32 +159,32 @@ public class Java8 implements VMPlugin {
     @Deprecated
     protected int getElementCode(final ElementType value) {
         switch (value) {
-            case TYPE:
-                return AnnotationNode.TYPE_TARGET;
-            case CONSTRUCTOR:
-                return AnnotationNode.CONSTRUCTOR_TARGET;
-            case METHOD:
-                return AnnotationNode.METHOD_TARGET;
-            case FIELD:
-                return AnnotationNode.FIELD_TARGET;
-            case PARAMETER:
-                return AnnotationNode.PARAMETER_TARGET;
-            case LOCAL_VARIABLE:
-                return AnnotationNode.LOCAL_VARIABLE_TARGET;
-            case ANNOTATION_TYPE:
-                return AnnotationNode.ANNOTATION_TARGET;
-            case PACKAGE:
-                return AnnotationNode.PACKAGE_TARGET;
-            case TYPE_PARAMETER:
-                return AnnotationNode.TYPE_PARAMETER_TARGET;
-            case TYPE_USE:
-                return AnnotationNode.TYPE_USE_TARGET;
-            default:
-                // falls through
+          case TYPE:
+            return AnnotationNode.TYPE_TARGET;
+          case CONSTRUCTOR:
+            return AnnotationNode.CONSTRUCTOR_TARGET;
+          case METHOD:
+            return AnnotationNode.METHOD_TARGET;
+          case FIELD:
+            return AnnotationNode.FIELD_TARGET;
+          case PARAMETER:
+            return AnnotationNode.PARAMETER_TARGET;
+          case LOCAL_VARIABLE:
+            return AnnotationNode.LOCAL_VARIABLE_TARGET;
+          case ANNOTATION_TYPE:
+            return AnnotationNode.ANNOTATION_TARGET;
+          case PACKAGE:
+            return AnnotationNode.PACKAGE_TARGET;
+          case TYPE_PARAMETER:
+            return AnnotationNode.TYPE_PARAMETER_TARGET;
+          case TYPE_USE:
+            return AnnotationNode.TYPE_USE_TARGET;
+          default:
+            // falls through
         }
         String name = value.name();
         if ("MODULE".equals(name)) { // JDK 9+
-            return AnnotationNode.TYPE_TARGET; // TODO Add MODULE_TARGET too?
+            return AnnotationNode.TYPE_TARGET;
         } else if ("RECORD_COMPONENT".equals(name)) { // JDK 16+
             return AnnotationNode.RECORD_COMPONENT_TARGET;
         } else {
@@ -196,27 +194,7 @@ public class Java8 implements VMPlugin {
 
     @Override
     public void setAdditionalClassInformation(final ClassNode cn) {
-        setGenericsTypes(cn);
-    }
-
-    private void setGenericsTypes(final ClassNode cn) {
-        TypeVariable[] tvs = cn.getTypeClass().getTypeParameters();
-        GenericsType[] gts = configureTypeVariable(tvs);
-        cn.setGenericsTypes(gts);
-    }
-
-    private GenericsType[] configureTypeVariable(final TypeVariable[] tvs) {
-        final int n = tvs.length;
-        if (n == 0) return null;
-        GenericsType[] gts = new GenericsType[n];
-        for (int i = 0; i < n; i += 1) {
-            gts[i] = configureTypeVariableDefinition(tvs[i]);
-        }
-        return gts;
-    }
-
-    private GenericsType configureTypeVariableDefinition(final TypeVariable tv) {
-        return configureTypeVariableDefinition(configureTypeVariableReference(tv.getName()), configureTypes(tv.getBounds()));
+        cn.setGenericsTypes(configureTypeParameters(cn.getTypeClass().getTypeParameters()));
     }
 
     private ClassNode[] configureTypes(final Type[] types) {
@@ -237,10 +215,10 @@ public class Java8 implements VMPlugin {
         } else if (type instanceof GenericArrayType) {
             return configureGenericArray((GenericArrayType) type);
         } else if (type instanceof TypeVariable) {
-            return configureTypeVariableReference(((TypeVariable) type).getName());
+            return configureTypeVariableReference(((TypeVariable<?>) type).getName());
         } else if (type instanceof Class) {
             return configureClass((Class<?>) type);
-        } else if (type==null) {
+        } else if (type == null) {
             throw new GroovyBugError("Type is null. Most probably you let a transform reuse existing ClassNodes with generics information, that is now used in a wrong context.");
         } else {
             throw new GroovyBugError("unknown type: " + type + " := " + type.getClass());
@@ -295,6 +273,18 @@ public class Java8 implements VMPlugin {
         return gts;
     }
 
+    private GenericsType[] configureTypeParameters(final TypeVariable<?>[] tp) {
+        final int n = tp.length;
+        if (n == 0) return null;
+        GenericsType[] gt = new GenericsType[n];
+        for (int i = 0; i < n; i += 1) {
+            ClassNode t = configureTypeVariableReference(tp[i].getName());
+            ClassNode[] bounds = configureTypes(tp[i].getBounds());
+            gt[i] = configureTypeVariableDefinition(t, bounds);
+        }
+        return gt;
+    }
+
     //
 
     @Override
@@ -387,8 +377,7 @@ public class Java8 implements VMPlugin {
 
     @Override
     public void configureAnnotationNodeFromDefinition(final AnnotationNode definition, final AnnotationNode root) {
-        ClassNode type = definition.getClassNode();
-        final String typeName = type.getName();
+        String typeName = definition.getClassNode().getName();
         if ("java.lang.annotation.Retention".equals(typeName)) {
             Expression exp = definition.getMember("value");
             if (!(exp instanceof PropertyExpression)) return;
@@ -399,16 +388,16 @@ public class Java8 implements VMPlugin {
         } else if ("java.lang.annotation.Target".equals(typeName)) {
             Expression exp = definition.getMember("value");
             if (!(exp instanceof ListExpression)) return;
-            ListExpression le = (ListExpression) exp;
-            int bitmap = 0;
-            for (Expression e : le.getExpressions()) {
+            ListExpression list = (ListExpression) exp;
+            int targets = 0;
+            for (Expression e : list.getExpressions()) {
                 if (!(e instanceof PropertyExpression)) return;
                 PropertyExpression element = (PropertyExpression) e;
                 String name = element.getPropertyAsString();
-                ElementType value = ElementType.valueOf(name);
-                bitmap |= getElementCode(value);
+                ElementType type = ElementType.valueOf(name);
+                targets |= getElementCode(type);
             }
-            root.setAllowedTargets(bitmap);
+            root.setAllowedTargets(targets);
         }
     }
 
@@ -418,29 +407,32 @@ public class Java8 implements VMPlugin {
             Class<?> clazz = classNode.getTypeClass();
             Field[] fields = clazz.getDeclaredFields();
             for (Field f : fields) {
-                ClassNode ret = makeClassNode(compileUnit, f.getGenericType(), f.getType());
-                FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), ret, classNode, null);
+                ClassNode rt = makeClassNode(compileUnit, f.getGenericType(), f.getType());
+                FieldNode fn = new FieldNode(f.getName(), f.getModifiers(), rt, classNode, null);
                 setAnnotationMetaData(f.getAnnotations(), fn);
                 classNode.addField(fn);
             }
             Method[] methods = clazz.getDeclaredMethods();
             for (Method m : methods) {
-                ClassNode ret = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType());
+                ClassNode rt = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType());
                 Parameter[] params = makeParameters(compileUnit, m.getGenericParameterTypes(), m.getParameterTypes(), m.getParameterAnnotations(), m);
                 ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes());
-                MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), ret, params, exceptions, null);
-                mn.setSynthetic(m.isSynthetic());
-                setMethodDefaultValue(mn, m);
+                MethodNode mn = new MethodNode(m.getName(), m.getModifiers(), rt, params, exceptions, null);
                 setAnnotationMetaData(m.getAnnotations(), mn);
-                mn.setGenericsTypes(configureTypeVariable(m.getTypeParameters()));
+                if (true) { // TODO: GROOVY-10862
+                    mn.setAnnotationDefault(true);
+                    mn.setCode(new ReturnStatement(new ConstantExpression(m.getDefaultValue())));
+                }
+                mn.setGenericsTypes(configureTypeParameters(m.getTypeParameters()));
+                mn.setSynthetic(m.isSynthetic());
                 classNode.addMethod(mn);
             }
-            Constructor[] constructors = clazz.getDeclaredConstructors();
-            for (Constructor ctor : constructors) {
-                Parameter[] params = makeParameters(compileUnit, ctor.getGenericParameterTypes(), ctor.getParameterTypes(), getConstructorParameterAnnotations(ctor), ctor);
-                ClassNode[] exceptions = makeClassNodes(compileUnit, ctor.getGenericExceptionTypes(), ctor.getExceptionTypes());
-                ConstructorNode cn = classNode.addConstructor(ctor.getModifiers(), params, exceptions, null);
-                setAnnotationMetaData(ctor.getAnnotations(), cn);
+            Constructor<?>[] constructors = clazz.getDeclaredConstructors();
+            for (Constructor<?> c : constructors) {
+                Parameter[] params = makeParameters(compileUnit, c.getGenericParameterTypes(), c.getParameterTypes(), getConstructorParameterAnnotations(c), c);
+                ClassNode[] exceptions = makeClassNodes(compileUnit, c.getGenericExceptionTypes(), c.getExceptionTypes());
+                ConstructorNode cn = classNode.addConstructor(c.getModifiers(), params, exceptions, null);
+                setAnnotationMetaData(c.getAnnotations(), cn);
             }
 
             Class<?> sc = clazz.getSuperclass();
@@ -561,7 +553,7 @@ public class Java8 implements VMPlugin {
         return back.getPlainNodeReference();
     }
 
-    private Parameter[] makeParameters(final CompileUnit cu, final Type[] types, final Class<?>[] cls, final Annotation[][] parameterAnnotations, final java.lang.reflect.Member member) {
+    private Parameter[] makeParameters(final CompileUnit cu, final Type[] types, final Class<?>[] cls, final Annotation[][] parameterAnnotations, final Member member) {
         Parameter[] params = Parameter.EMPTY_ARRAY;
         final int n = types.length;
         if (n > 0) {
@@ -576,7 +568,7 @@ public class Java8 implements VMPlugin {
         return params;
     }
 
-    protected void fillParameterNames(final String[] names, final java.lang.reflect.Member member) {
+    protected void fillParameterNames(final String[] names, final Member member) {
         try {
             java.lang.reflect.Parameter[] parameters = ((java.lang.reflect.Executable) member).getParameters();
             for (int i = 0, n = names.length; i < n; i += 1) {
@@ -587,6 +579,8 @@ public class Java8 implements VMPlugin {
         }
     }
 
+    //--------------------------------------------------------------------------
+
     /**
      * The following scenarios can not set accessible, i.e. the return value is false
      * 1) SecurityException occurred
@@ -636,27 +630,13 @@ public class Java8 implements VMPlugin {
     }
 
     @Override
-    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod, final Class<?> caller) {
-        return metaMethod;
-    }
-
-    @Override
-    @Deprecated
-    @SuppressWarnings("removal") // TODO a future Groovy version will remove this method
-    public <T> T doPrivileged(java.security.PrivilegedAction<T> action) {
-        throw new UnsupportedOperationException("doPrivileged is no longer supported");
-    }
-
-    @Override
-    @Deprecated
-    @SuppressWarnings("removal") // TODO a future Groovy version will remove this method
-    public <T> T doPrivileged(java.security.PrivilegedExceptionAction<T> action) throws java.security.PrivilegedActionException {
-        throw new UnsupportedOperationException("doPrivileged is no longer supported");
+    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod) {
+        return transformMetaMethod(metaClass, metaMethod, null);
     }
 
     @Override
-    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod) {
-        return transformMetaMethod(metaClass, metaMethod, null);
+    public MetaMethod transformMetaMethod(final MetaClass metaClass, final MetaMethod metaMethod, final Class<?> caller) {
+        return metaMethod;
     }
 
     @Override
@@ -664,48 +644,40 @@ public class Java8 implements VMPlugin {
         IndyInterface.invalidateSwitchPoints();
     }
 
-    @Deprecated
-    protected MethodHandles.Lookup getLookup(final Object receiver) {
-        Optional<MethodHandles.Lookup> lookup = Optional.empty();
-        if (receiver instanceof GroovyObject) {
-            lookup = GroovyObjectHelper.lookup((GroovyObject) receiver);
-        }
-        return lookup.orElseGet(() -> newLookup(receiver.getClass()));
-    }
-
     @Override
     public Object getInvokeSpecialHandle(final Method method, final Object receiver) {
+        final Class<?> receiverClass = receiver.getClass();
         try {
-            return getLookup(receiver)
-                    .unreflectSpecial(method, receiver.getClass())
-                    .bindTo(receiver);
-        } catch (ReflectiveOperationException e) {
-            return getInvokeSpecialHandleFallback(method, receiver);
+            return getLookup(receiver).unreflectSpecial(method, receiverClass).bindTo(receiver);
+        } catch (ReflectiveOperationException e1) {
+            if (!method.isAccessible()) {
+                doPrivilegedInternal(() -> ReflectionUtils.trySetAccessible(method));
+            }
+            final Class<?> declaringClass = method.getDeclaringClass();
+            try {
+                return newLookup(declaringClass).unreflectSpecial(method, declaringClass).bindTo(receiver);
+            } catch (ReflectiveOperationException e2) {
+                Error e3 = new GroovyBugError(e1);
+                e3.addSuppressed(e2);
+                throw e3;
+            }
         }
     }
 
-    private Object getInvokeSpecialHandleFallback(final Method method, final Object receiver) {
-        if (!method.isAccessible()) {
-            doPrivilegedInternal(() -> {
-                ReflectionUtils.trySetAccessible(method);
-                return null;
-            });
-        }
-        Class<?> declaringClass = method.getDeclaringClass();
-        try {
-            return newLookup(declaringClass).unreflectSpecial(method, declaringClass).bindTo(receiver);
-        } catch (ReflectiveOperationException e) {
-            throw new GroovyBugError(e);
-        }
-    }
-    @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action
-    private static <T> T doPrivilegedInternal(java.security.PrivilegedAction<T> action) {
-        return java.security.AccessController.doPrivileged(action);
+    @Override
+    public Object invokeHandle(final Object handle, final Object[] arguments) throws Throwable {
+        return ((MethodHandle) handle).invokeWithArguments(arguments);
     }
 
-    @Override
-    public Object invokeHandle(final Object handle, final Object[] args) throws Throwable {
-        return ((MethodHandle) handle).invokeWithArguments(args);
+    //--------------------------------------------------------------------------
+
+    @Deprecated
+    protected MethodHandles.Lookup getLookup(final Object receiver) {
+        if (receiver instanceof GroovyObject) {
+            return GroovyObjectHelper.lookup((GroovyObject) receiver)
+                    .orElseGet(() -> newLookup(receiver.getClass()));
+        }
+        return newLookup(receiver.getClass());
     }
 
     protected MethodHandles.Lookup newLookup(final Class<?> declaringClass) {
@@ -735,10 +707,7 @@ public class Java8 implements VMPlugin {
             try {
                 if (!lookup.isAccessible()) {
                     final Constructor<MethodHandles.Lookup> finalReference = lookup;
-                    doPrivilegedInternal(() -> {
-                        ReflectionUtils.trySetAccessible(finalReference);
-                        return null;
-                    });
+                    doPrivilegedInternal(() -> ReflectionUtils.trySetAccessible(finalReference));
                 }
             } catch (SecurityException ignore) {
                 lookup = null;
@@ -748,4 +717,21 @@ public class Java8 implements VMPlugin {
             LOOKUP_Constructor = lookup;
         }
     }
+
+    @Override
+    @Deprecated
+    public <T> T doPrivileged(final java.security.PrivilegedAction<T> action) {
+        throw new UnsupportedOperationException("doPrivileged is no longer supported");
+    }
+
+    @Override
+    @Deprecated
+    public <T> T doPrivileged(final java.security.PrivilegedExceptionAction<T> action) {
+        throw new UnsupportedOperationException("doPrivileged is no longer supported");
+    }
+
+    @SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action
+    private static <T> T doPrivilegedInternal(final java.security.PrivilegedAction<T> action) {
+        return java.security.AccessController.doPrivileged(action);
+    }
 }
diff --git a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
index 6366dceb81..49f381a3af 100644
--- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
@@ -769,6 +769,11 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase {
             def file = Pogo10756.files[0]
             file?.name
         """
+
+        assertScript """import ${Pogo10756.name.replace('$','.')}
+            def files = Pogo10756.files
+            files*.name
+        """
     }
 
     static class Pogo10756 {
diff --git a/src/test/org/codehaus/groovy/ast/decompiled/AsmDecompilerTest.groovy b/src/test/org/codehaus/groovy/ast/decompiled/AsmDecompilerTest.groovy
index a02ae19abd..bbe96f953f 100644
--- a/src/test/org/codehaus/groovy/ast/decompiled/AsmDecompilerTest.groovy
+++ b/src/test/org/codehaus/groovy/ast/decompiled/AsmDecompilerTest.groovy
@@ -18,7 +18,6 @@
  */
 package org.codehaus.groovy.ast.decompiled
 
-import junit.framework.TestCase
 import org.codehaus.groovy.ast.AnnotationNode
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
@@ -26,24 +25,27 @@ import org.codehaus.groovy.ast.decompiled.support.*
 import org.codehaus.groovy.ast.expr.*
 import org.codehaus.groovy.control.ClassNodeResolver
 import org.codehaus.groovy.control.CompilationUnit
-import org.objectweb.asm.Opcodes
+import org.junit.Test
 
-final class AsmDecompilerTest extends TestCase {
+import static java.lang.reflect.Modifier.*
 
-    void "test decompile class"() {
+final class AsmDecompilerTest {
+
+    @Test
+    void "basic class"() {
         ClassNode node = decompile()
-        assert AsmDecompilerTestData.name == node.name
-        assert (node.modifiers & Opcodes.ACC_PUBLIC) != 0
 
+        assert node.name == AsmDecompilerTestData.name
         assert !node.genericsPlaceHolder
+        assert isPublic(node.modifiers)
         assert node.usingGenerics
 
         def t = node.genericsTypes[0]
         assert t.name == 'T'
         assert t.placeholder
-        assert t.type.name == Object.name
         assert t.type.genericsPlaceHolder
-        assert !t.lowerBound
+        assert t.type.name == List.name // erasure of "T extends List<? super T>"
+        assert t.lowerBound == null
 
         def list = t.upperBounds[0]
         assert list.name == List.name
@@ -52,35 +54,41 @@ final class AsmDecompilerTest extends TestCase {
 
         def wildcard = list.genericsTypes[0]
         assert wildcard.wildcard
-        assert !wildcard.type.genericsPlaceHolder //todo?
+        assert !wildcard.type.genericsPlaceHolder
+        assert wildcard.type.name == Object.name // erasure of "? super T"
         assert !wildcard.upperBounds
 
         def tRef = wildcard.lowerBound
         assert tRef.genericsPlaceHolder
         assert tRef.usingGenerics
         assert tRef.name == Object.name
+        assert tRef.unresolvedName == 'T'
         assert tRef.genericsTypes[0].name == 'T'
 
         def v = node.genericsTypes[1]
         assert v.name == 'V'
         assert v.placeholder
-        assert v.upperBounds[0].toString() == Object.name
+        assert v.type.name == Object.name
+        assert v.upperBounds[0].name == Object.name
     }
 
-    void "test method object return type"() {
-        def method = decompile().getDeclaredMethod("objectMethod")
+    @Test
+    void "method object return type"() {
+        def method = decompile().getDeclaredMethod('objectMethod')
         assert method.returnType.name == ClassNode.name
     }
 
-    void "test method primitive array return type"() {
-        def method = decompile().getDeclaredMethod("primitiveArrayMethod")
+    @Test
+    void "method primitive array return type"() {
+        def method = decompile().getDeclaredMethod('primitiveArrayMethod')
         assert method.returnType.array
         assert method.returnType.componentType.array
         assert method.returnType.componentType.componentType == ClassHelper.int_TYPE
     }
 
-    void "test method parameters and exceptions"() {
-        def method = decompile().getDeclaredMethods("withParametersThrowing")[0]
+    @Test
+    void "method parameters and exceptions"() {
+        def method = decompile().getDeclaredMethods('withParametersThrowing')[0]
 
         assert method.parameters.length == 2
         assert method.parameters[0].type == ClassHelper.int_TYPE
@@ -90,17 +98,20 @@ final class AsmDecompilerTest extends TestCase {
         assert method.exceptions[0].name == IOException.name
     }
 
-    void "test field"() {
-        def field = decompile().getDeclaredField("aField")
+    @Test
+    void "basic field"() {
+        def field = decompile().getDeclaredField('aField')
         assert field.type.name == Object.name
     }
 
-    void "test constructor"() {
+    @Test
+    void "constructor"() {
         def constructor = decompile().getDeclaredConstructors()[0]
         assert constructor.parameters[0].type == ClassHelper.boolean_TYPE
     }
 
-    void "test supers"() {
+    @Test
+    void "supers"() {
         def node = decompile()
         assert node.superClass.name == SuperClass.name
         assert !node.superClass.usingGenerics
@@ -126,12 +137,13 @@ final class AsmDecompilerTest extends TestCase {
         assert !string.type.usingGenerics
     }
 
-    void "test simple class annotations"() {
+    @Test
+    void "simple class annotations"() {
         def node = decompile().annotations[0]
 
         assert node.classNode.name == Anno.name
 
-        assert ((ConstantExpression) node.members.stringAttr).value == "s"
+        assert ((ConstantExpression) node.members.stringAttr).value == 's'
         assert !node.members.booleanAttr
         assert ((ClassExpression) node.members.clsAttr).type.name == String.name
 
@@ -139,35 +151,40 @@ final class AsmDecompilerTest extends TestCase {
         assert ((PropertyExpression) node.members.enumAttr).objectExpression.type.name == SomeEnum.name
     }
 
-    void "test member annotations"() {
+    @Test
+    void "member annotations"() {
         def node = decompile()
-        assert node.getDeclaredField("aField").annotations[0].classNode.name == Anno.name
-        assert node.getDeclaredMethod("objectMethod").annotations[0].classNode.name == Anno.name
-        assert !node.getDeclaredMethods("withParametersThrowing")[0].annotations
+        assert node.getDeclaredField('aField').annotations[0].classNode.name == Anno.name
+        assert node.getDeclaredMethod('objectMethod').annotations[0].classNode.name == Anno.name
+        assert !node.getDeclaredMethods('withParametersThrowing')[0].annotations
     }
 
-    void "test parameter annotations"() {
+    @Test
+    void "parameter annotations"() {
         def node = decompile()
-        def params = node.getDeclaredMethods("withParametersThrowing")[0].parameters
+        def params = node.getDeclaredMethods('withParametersThrowing')[0].parameters
         assert params[0].annotations.collect { it.classNode.name } == [Anno.name]
         assert !params[1].annotations
     }
 
-    void "test primitive array attribute"() {
+    @Test
+    void "primitive array attribute"() {
         def node = decompile().annotations[0]
 
         def list = ((ListExpression) node.members.intArrayAttr).expressions
         assert list.collect { ((ConstantExpression) it).value } == [4, 2]
     }
 
-    void "test class array attribute"() {
+    @Test
+    void "class array attribute"() {
         def node = decompile().annotations[0]
 
         def list = ((ListExpression) node.members.classArrayAttr).expressions
         assert list.collect { ((ClassExpression) it).type.name } == [AsmDecompilerTestData.name]
     }
 
-    void "test annotation array attribute"() {
+    @Test
+    void "annotation array attribute"() {
         def node = decompile().annotations[0]
 
         def list = ((ListExpression) node.members.annoArrayAttr).expressions
@@ -181,14 +198,16 @@ final class AsmDecompilerTest extends TestCase {
         assert ((ConstantExpression) annotationNode.members.booleanAttr).value == false
     }
 
-    void "test annotation default method"() {
-        def method = decompile(Anno.name).getDeclaredMethod('booleanAttr')
+    @Test
+    void "annotation default method"() {
+        def method = decompile(Anno).getDeclaredMethod('booleanAttr')
         assert method.hasAnnotationDefault()
         assert method.code
     }
 
-    void "test annotation retention and targets"() {
-        def anno = decompile().getAnnotations(decompile(Anno.name))[0]
+    @Test
+    void "annotation retention and targets"() {
+        def anno = decompile().getAnnotations(decompile(Anno))[0]
         assert anno.hasRuntimeRetention()
         assert !anno.hasClassRetention()
         assert !anno.hasSourceRetention()
@@ -198,8 +217,9 @@ final class AsmDecompilerTest extends TestCase {
         assert !anno.isTargetAllowed(AnnotationNode.LOCAL_VARIABLE_TARGET)
     }
 
-    void "test enum field"() {
-        def node = decompile(SomeEnum.name).plainNodeReference
+    @Test
+    void "enum field"() {
+        def node = decompile(SomeEnum).plainNodeReference
         for (s in ['FOO', 'BAR']) {
             def field = node.getDeclaredField(s)
             assert field
@@ -207,8 +227,9 @@ final class AsmDecompilerTest extends TestCase {
         }
     }
 
-    void "test generic method"() {
-        def method = decompile().getDeclaredMethods("genericMethod")[0]
+    @Test
+    void "generic method"() {
+        def method = decompile().getDeclaredMethods('genericMethod')[0]
 
         assert method.genericsTypes.size() == 2
         assert method.genericsTypes[0].name == 'A'
@@ -240,18 +261,21 @@ final class AsmDecompilerTest extends TestCase {
         assert wildcard.upperBounds == null
     }
 
-    void "test non-generic exceptions"() {
-        def method = decompile().getDeclaredMethods("nonGenericExceptions")[0]
+    @Test
+    void "non-generic exceptions"() {
+        def method = decompile().getDeclaredMethods('nonGenericExceptions')[0]
         assert method.exceptions[0].name == IOException.name
     }
 
-    void "test non-generic parameters"() {
-        def method = decompile().getDeclaredMethods("nonGenericParameters")[0]
+    @Test
+    void "non-generic parameters"() {
+        def method = decompile().getDeclaredMethods('nonGenericParameters')[0]
         assert method.parameters[0].type == ClassHelper.boolean_TYPE
     }
 
-    void "test generic field"() {
-        def type = decompile().getDeclaredField("genericField").type
+    @Test
+    void "generic field"() {
+        def type = decompile().getDeclaredField('genericField').type
         assert type.name == List.name
         assert type.usingGenerics
 
@@ -262,83 +286,92 @@ final class AsmDecompilerTest extends TestCase {
         assert tRef.genericsTypes[0].name == 'T'
     }
 
-    void "test non-trivial erasure"() {
-        def cls = decompile(NonTrivialErasure.name)
+    @Test
+    void "non-trivial erasure"() {
+        def cls = decompile(NonTrivialErasure)
 
-        def method = cls.getDeclaredMethods("method")[0]
+        def method = cls.getDeclaredMethods('method')[0]
         assert method.returnType.toString() == 'V -> java.lang.RuntimeException'
         assert method.parameters[0].type.toString() == 'V -> java.lang.RuntimeException'
         assert method.exceptions[0].toString() == 'V -> java.lang.RuntimeException'
 
-        def field = cls.getDeclaredField("field")
+        def field = cls.getDeclaredField('field')
         assert field.type.toString() == 'V -> java.lang.RuntimeException'
     }
 
-    void "test static inner class"() {
-        ClassNode cn = decompile(AsmDecompilerTestData.InnerStatic.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) != 0
+    @Test
+    void "static inner class"() {
+        ClassNode cn = decompile(AsmDecompilerTestData.InnerStatic)
+        assert isStatic(cn.modifiers)
     }
 
-    void "test static inner with dollar"() {
-        ClassNode cn = decompile(AsmDecompilerTestData.Inner$WithDollar.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) != 0
+    @Test
+    void "static inner with dollar"() {
+        ClassNode cn = decompile(AsmDecompilerTestData.Inner$WithDollar)
+        assert isStatic(cn.modifiers)
     }
 
-    void "test static inner classes with same name"() {
-        ClassNode cn = decompile(Groovy8632Abstract.Builder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) != 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) != 0
+    @Test
+    void "static inner classes with same name"() {
+        ClassNode cn = decompile(Groovy8632Abstract.Builder)
+        assert isStatic(cn.modifiers)
+        assert isAbstract(cn.modifiers)
 
-        cn = decompile(Groovy8632.Builder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) != 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) == 0
+        cn = decompile(Groovy8632.Builder)
+        assert isStatic(cn.modifiers)
+        assert !isAbstract(cn.modifiers)
 
-        cn = decompile(Groovy8632Groovy.Builder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) != 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) == 0
+        cn = decompile(Groovy8632Groovy.Builder)
+        assert isStatic(cn.modifiers)
+        assert !isAbstract(cn.modifiers)
     }
 
-    void "test inner classes with same name"() {
-        ClassNode cn = decompile(Groovy8632Abstract.InnerBuilder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) == 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) != 0
+    @Test
+    void "inner classes with same name"() {
+        ClassNode cn = decompile(Groovy8632Abstract.InnerBuilder)
+        assert !isStatic(cn.modifiers)
+        assert isAbstract(cn.modifiers)
 
-        cn = decompile(Groovy8632.InnerBuilder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) == 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) == 0
+        cn = decompile(Groovy8632.InnerBuilder)
+        assert !isStatic(cn.modifiers)
+        assert !isAbstract(cn.modifiers)
 
-        cn = decompile(Groovy8632Groovy.InnerBuilder.name)
-        assert (cn.modifiers & Opcodes.ACC_STATIC) == 0
-        assert (cn.modifiers & Opcodes.ACC_ABSTRACT) == 0
+        cn = decompile(Groovy8632Groovy.InnerBuilder)
+        assert !isStatic(cn.modifiers)
+        assert !isAbstract(cn.modifiers)
     }
 
-    void "test private inner class"() {
-        ClassNode cn = decompile(Groovy8632.InnerPrivate.name)
-        assert (cn.modifiers & Opcodes.ACC_PRIVATE) != 0
-        assert (cn.modifiers & Opcodes.ACC_PUBLIC) == 0
+    @Test
+    void "private inner class"() {
+        ClassNode cn = decompile(Groovy8632.InnerPrivate)
+        assert isPrivate(cn.modifiers)
+        assert !isPublic(cn.modifiers)
     }
 
-    void "test protected inner class"() {
-        ClassNode cn = decompile(Groovy8632.InnerProtected.name)
-        assert (cn.modifiers & Opcodes.ACC_PROTECTED) != 0
-        assert (cn.modifiers & Opcodes.ACC_PUBLIC) == 0
+    @Test
+    void "protected inner class"() {
+        ClassNode cn = decompile(Groovy8632.InnerProtected)
+        assert isProtected(cn.modifiers)
+        assert !isPublic(cn.modifiers)
     }
 
-    void "test non-parameterized generics"() {
-        assert decompile().getDeclaredMethod("nonParameterizedGenerics").genericsTypes == null
+    @Test
+    void "non-parameterized generics"() {
+        assert decompile().getDeclaredMethod('nonParameterizedGenerics').genericsTypes == null
     }
 
-    void "test non-static parameterized inner"() {
-        def asmType = decompile().getDeclaredMethod("returnInner").returnType
-        def jvmType = new ClassNode(AsmDecompilerTestData).getDeclaredMethod("returnInner").returnType
+    @Test
+    void "non-static parameterized inner"() {
+        def asmType = decompile().getDeclaredMethod('returnInner').returnType
+        def jvmType = new ClassNode(AsmDecompilerTestData).getDeclaredMethod('returnInner').returnType
         assert asmType == jvmType
-        assert asmType.genericsTypes.collect { it.name } == jvmType.genericsTypes.collect { it.name }
+        assert asmType.genericsTypes*.name == jvmType.genericsTypes*.name
     }
 
     //--------------------------------------------------------------------------
 
-    private static ClassNode decompile(String className = AsmDecompilerTestData.name) {
-        def classFileName = className.replace('.', '/') + '.class'
+    private static ClassNode decompile(Class clazz = AsmDecompilerTestData) {
+        def classFileName = clazz.name.replace('.', '/') + '.class'
         def resource = AsmDecompilerTest.classLoader.getResource(classFileName)
         def stub = AsmDecompiler.parseClass(resource)