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 2022/01/25 18:20:37 UTC

[groovy] branch master updated: GROOVY-5001, GROOVY-5491, GROOVY-6144: map property precedence

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 051d3cf3 GROOVY-5001, GROOVY-5491, GROOVY-6144: map property precedence
051d3cf3 is described below

commit 051d3cf3ef29a521137b9d6b71cb8097d077d46b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jan 25 12:20:29 2022 -0600

    GROOVY-5001, GROOVY-5491, GROOVY-6144: map property precedence
---
 src/main/java/groovy/lang/MetaClassImpl.java       | 228 ++++++++++-----------
 .../classgen/asm/sc/StaticTypesCallSiteWriter.java |  12 +-
 .../callsite/GetEffectivePojoPropertySite.java     |  22 +-
 src/test/groovy/MapTest.groovy                     | 118 +++++++++--
 src/test/groovy/bugs/Groovy662.groovy              |  90 ++++++++
 src/test/groovy/bugs/Groovy662Bug.groovy           |  96 ---------
 .../{Groovy8065Bug.groovy => Groovy8065.groovy}    |  25 ++-
 .../stc/ArraysAndCollectionsSTCTest.groovy         |  23 ++-
 .../ArraysAndCollectionsStaticCompileTest.groovy   |  13 --
 9 files changed, 345 insertions(+), 282 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 9cb38db..e6aea60 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -1960,100 +1960,105 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
      * @return the given property's value on the object
      */
     @Override
-    public Object getProperty(Class sender, Object object, String name, boolean useSuper, boolean fromInsideClass) {
+    public Object getProperty(final Class sender, final Object object, final String name, final boolean useSuper, final boolean fromInsideClass) {
 
         //----------------------------------------------------------------------
         // handling of static
         //----------------------------------------------------------------------
-        boolean isStatic = theClass != Class.class && object instanceof Class;
+        boolean isStatic = (theClass != Class.class && object instanceof Class);
         if (isStatic && object != theClass) {
-            MetaClass mc = registry.getMetaClass((Class) object);
+            MetaClass mc = registry.getMetaClass((Class<?>) object);
             return mc.getProperty(sender, object, name, useSuper, false);
         }
 
         checkInitalised();
 
         //----------------------------------------------------------------------
-        // turn getProperty on a Map to get on the Map itself
+        // getter
         //----------------------------------------------------------------------
-        if (!isStatic && this.isMap) {
-            return ((Map) object).get(name);
-        }
-
         Tuple2<MetaMethod, MetaProperty> methodAndProperty = createMetaMethodAndMetaProperty(sender, sender, name, useSuper, isStatic);
         MetaMethod method = methodAndProperty.getV1();
 
-        //----------------------------------------------------------------------
-        // getter
-        //----------------------------------------------------------------------
-        MetaProperty mp = methodAndProperty.getV2();
+        if (method == null || isSpecialProperty(name)) {
+            //------------------------------------------------------------------
+            // public field
+            //------------------------------------------------------------------
+            MetaProperty mp = methodAndProperty.getV2();
+            if (mp != null && Modifier.isPublic(mp.getModifiers())) {
+                try {
+                    return mp.getProperty(object);
+                } catch (IllegalArgumentException | CacheAccessControlException e) {
+                    // can't access the field directly but there may be a getter
+                    mp = null;
+                }
+            }
 
-        //----------------------------------------------------------------------
-        // field
-        //----------------------------------------------------------------------
-        if (method == null && mp != null) {
-            try {
-                return mp.getProperty(object);
-            } catch (IllegalArgumentException | CacheAccessControlException e) {
-                // can't access the field directly but there may be a getter
-                mp = null;
+            //------------------------------------------------------------------
+            // java.util.Map get method
+            //------------------------------------------------------------------
+            if (isMap && !isStatic) {
+                return ((Map<?,?>) object).get(name);
             }
-        }
 
-        // check for propertyMissing provided through a category
-        Object[] arguments = EMPTY_ARGUMENTS;
-        if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) {
-            method = getCategoryMethodGetter(sender, PROPERTY_MISSING, true);
-            if (method != null) arguments = new Object[]{name};
+            //------------------------------------------------------------------
+            // non-public field
+            //------------------------------------------------------------------
+            if (mp != null) {
+                try {
+                    return mp.getProperty(object);
+                } catch (IllegalArgumentException | CacheAccessControlException e) {
+                }
+            }
         }
 
-
         //----------------------------------------------------------------------
-        // generic get method
+        // propertyMissing (via category) or generic get method
         //----------------------------------------------------------------------
-        // check for a generic get method provided through a category
+        Object[] arguments = EMPTY_ARGUMENTS;
         if (method == null && !useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) {
-            method = getCategoryMethodGetter(sender, "get", true);
+            // check for propertyMissing provided through a category; TODO:should this have lower precedence?
+            method = getCategoryMethodGetter(sender, PROPERTY_MISSING, true);
+            if (method == null) {
+                // check for a generic get method provided through a category
+                method = getCategoryMethodGetter(sender, "get", true);
+            }
             if (method != null) arguments = new Object[]{name};
         }
-
-        // the generic method is valid, if available (!=null), if static or
-        // if it is not static and we do no static access
-        if (method == null && genericGetMethod != null && !(!genericGetMethod.isStatic() && isStatic)) {
+        if (method == null && genericGetMethod != null && (genericGetMethod.isStatic() || !isStatic)) {
             arguments = new Object[]{name};
             method = genericGetMethod;
         }
 
-        //----------------------------------------------------------------------
-        // special cases
-        //----------------------------------------------------------------------
         if (method == null) {
-            /* todo these special cases should be special MetaClasses maybe */
+            //------------------------------------------------------------------
+            // special cases
+            //------------------------------------------------------------------
+            // TODO: maybe these special cases should be special MetaClasses
             if (theClass != Class.class && object instanceof Class) {
                 MetaClass mc = registry.getMetaClass(Class.class);
                 return mc.getProperty(Class.class, object, name, useSuper, false);
             }
             if (object instanceof Collection) {
-                return DefaultGroovyMethods.getAt((Collection) object, name);
+                return DefaultGroovyMethods.getAt((Collection<?>) object, name);
             }
             if (object instanceof Object[]) {
                 return DefaultGroovyMethods.getAt(Arrays.asList((Object[]) object), name);
             }
             MetaMethod addListenerMethod = listeners.get(name);
             if (addListenerMethod != null) {
-                //TODO: one day we could try return the previously registered Closure listener for easy removal
+                // TODO: one day we could try return the previously registered Closure listener for easy removal
                 return null;
             }
         } else {
-            //----------------------------------------------------------------------
-            // executing the getter method
-            //----------------------------------------------------------------------
+            //------------------------------------------------------------------
+            // executing the method
+            //------------------------------------------------------------------
             MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method);
             return transformedMetaMethod.doMethodInvoke(object, arguments);
         }
 
         //----------------------------------------------------------------------
-        // error due to missing method/field
+        // missing property protocol
         //----------------------------------------------------------------------
         if (isStatic || object instanceof Class) {
             return invokeStaticMissingProperty(object, name, null, true);
@@ -2066,13 +2071,12 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         //----------------------------------------------------------------------
         // handling of static
         //----------------------------------------------------------------------
-        boolean isStatic = theClass != Class.class && object instanceof Class;
+        boolean isStatic = (theClass != Class.class && object instanceof Class);
         if (isStatic && object != theClass) {
             return new MetaProperty(name, Object.class) {
-                final MetaClass mc = registry.getMetaClass((Class) object);
-
                 @Override
                 public Object getProperty(Object object) {
+                    MetaClass mc = registry.getMetaClass((Class<?>) object);
                     return mc.getProperty(sender, object, name, useSuper, false);
                 }
 
@@ -2086,73 +2090,65 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         checkInitalised();
 
         //----------------------------------------------------------------------
-        // turn getProperty on a Map to get on the Map itself
+        // getter
         //----------------------------------------------------------------------
-        if (!isStatic && this.isMap) {
-            return new MetaProperty(name, Object.class) {
-                @Override
-                public Object getProperty(Object object) {
-                    return ((Map) object).get(name);
-                }
-
-                @Override
-                public void setProperty(Object object, Object newValue) {
-                    throw new UnsupportedOperationException();
-                }
-            };
-        }
-
         Tuple2<MetaMethod, MetaProperty> methodAndProperty = createMetaMethodAndMetaProperty(sender, theClass, name, useSuper, isStatic);
         MetaMethod method = methodAndProperty.getV1();
 
-        //----------------------------------------------------------------------
-        // getter
-        //----------------------------------------------------------------------
-        MetaProperty mp = methodAndProperty.getV2();
+        if (method == null || isSpecialProperty(name)) {
+            //------------------------------------------------------------------
+            // public field
+            //------------------------------------------------------------------
+            MetaProperty mp = methodAndProperty.getV2();
+            if (mp != null && Modifier.isPublic(mp.getModifiers())) {
+                return mp;
+            }
 
-        //----------------------------------------------------------------------
-        // field
-        //----------------------------------------------------------------------
-        if (method != null) {
-            MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method);
-            return new GetBeanMethodMetaProperty(name, transformedMetaMethod);
+            //----------------------------------------------------------------------
+            // java.util.Map get method
+            //----------------------------------------------------------------------
+            if (isMap && !isStatic) {
+                return new MetaProperty(name, Object.class) {
+                    @Override
+                    public Object getProperty(Object object) {
+                        return ((Map<?,?>) object).get(name);
+                    }
+
+                    @Override
+                    public void setProperty(Object object, Object newValue) {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+
+            //------------------------------------------------------------------
+            // non-public field
+            //------------------------------------------------------------------
+            if (mp != null) {
+                return mp;
+            }
         }
 
-        if (mp != null) {
-            return mp;
-//            try {
-//                return mp.getProperty(object);
-//            } catch (IllegalArgumentException e) {
-//                // can't access the field directly but there may be a getter
-//                mp = null;
-//            }
+        if (method != null) {
+            return new GetBeanMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, method));
         }
 
         //----------------------------------------------------------------------
         // generic get method
         //----------------------------------------------------------------------
-        // check for a generic get method provided through a category
         if (!useSuper && !isStatic && GroovyCategorySupport.hasCategoryInCurrentThread()) {
             method = getCategoryMethodGetter(sender, "get", true);
             if (method != null) {
-                MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method);
-                return new GetMethodMetaProperty(name, transformedMetaMethod);
+                return new GetMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, method));
             }
-
         }
-
-        // the generic method is valid, if available (!=null), if static or
-        // if it is not static and we do no static access
-        if (genericGetMethod != null && !(!genericGetMethod.isStatic() && isStatic)) {
-            method = genericGetMethod;
-            MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method);
-            return new GetMethodMetaProperty(name, transformedMetaMethod);
+        if (genericGetMethod != null && (genericGetMethod.isStatic() || !isStatic)) {
+            return new GetMethodMetaProperty(name, VM_PLUGIN.transformMetaMethod(this, genericGetMethod));
         }
 
         //----------------------------------------------------------------------
         // special cases
         //----------------------------------------------------------------------
-        /* todo these special cases should be special MetaClasses maybe */
         if (theClass != Class.class && object instanceof Class) {
             return new MetaProperty(name, Object.class) {
                 @Override
@@ -2171,7 +2167,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             return new MetaProperty(name, Object.class) {
                 @Override
                 public Object getProperty(Object object) {
-                    return DefaultGroovyMethods.getAt((Collection) object, name);
+                    return DefaultGroovyMethods.getAt((Collection<?>) object, name);
                 }
 
                 @Override
@@ -2195,7 +2191,6 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         }
         MetaMethod addListenerMethod = listeners.get(name);
         if (addListenerMethod != null) {
-            //TODO: one day we could try return the previously registered Closure listener for easy removal
             return new MetaProperty(name, Object.class) {
                 @Override
                 public Object getProperty(Object object) {
@@ -2238,10 +2233,17 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         };
     }
 
+    /**
+     * Object#getClass, Map#isEmpty, GroovyObject#getMetaClass
+     */
+    private boolean isSpecialProperty(final String name) {
+        return "class".equals(name) || (isMap && ("empty".equals(name) || "metaClass".equals(name)));
+    }
+
     private Tuple2<MetaMethod, MetaProperty> createMetaMethodAndMetaProperty(final Class senderForMP, final Class senderForCMG, final String name, final boolean useSuper, final boolean isStatic) {
         MetaMethod method = null;
         MetaProperty mp = getMetaProperty(senderForMP, name, useSuper, isStatic);
-        if ((mp == null || mp instanceof CachedField) && name.length() > 0 && isUpperCase(name.charAt(0)) && (name.length() < 2 || !isUpperCase(name.charAt(1))) && !"Class".equals(name)) {
+        if ((mp == null || mp instanceof CachedField) && !name.isEmpty() && isUpperCase(name.charAt(0)) && (name.length() < 2 || !isUpperCase(name.charAt(1))) && !"Class".equals(name) && !"MetaClass".equals(name)) {
             // GROOVY-9618 adjust because capitalised properties aren't stored as meta bean props
             MetaProperty saved = mp;
             mp = getMetaProperty(senderForMP, BeanUtils.decapitalize(name), useSuper, isStatic);
@@ -2794,8 +2796,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
      * @param fromInsideClass Whether the call was invoked from the inside or the outside of the class.
      */
     @Override
-    public void setProperty(Class sender, Object object, String name, Object newValue, boolean useSuper, boolean fromInsideClass) {
-        checkInitalised();
+    public void setProperty(final Class sender, final Object object, final String name, Object newValue, final boolean useSuper, final boolean fromInsideClass) {
 
         //----------------------------------------------------------------------
         // handling of static
@@ -2807,6 +2808,8 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             return;
         }
 
+        checkInitalised();
+
         //----------------------------------------------------------------------
         // Unwrap wrapped values fo now - the new MOP will handle them properly
         //----------------------------------------------------------------------
@@ -2873,16 +2876,16 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         // field
         //----------------------------------------------------------------------
         if (method == null && field != null) {
+            boolean mapInstance = (isMap && !isStatic);
             int modifiers = field.getModifiers();
             if (Modifier.isFinal(modifiers)) {
-                // GROOVY-5985
-                if (!isStatic && this.isMap) {
+                if (mapInstance) { // GROOVY-8065
                     ((Map) object).put(name, newValue);
                     return;
                 }
-                throw new ReadOnlyPropertyException(name, theClass);
+                throw new ReadOnlyPropertyException(name, theClass); // GROOVY-5985
             }
-            if (!this.isMap || Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) {
+            if (!mapInstance || Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) {
                 field.setProperty(object, newValue);
                 return;
             }
@@ -2896,16 +2899,13 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             method = getCategoryMethodSetter(sender, "set", true);
             if (method != null) arguments = new Object[]{name, newValue};
         }
-
-        // the generic method is valid, if available (!=null), if static or
-        // if it is not static and we do no static access
-        if (method == null && genericSetMethod != null && !(!genericSetMethod.isStatic() && isStatic)) {
+        if (method == null && genericSetMethod != null && (genericSetMethod.isStatic() || !isStatic)) {
             arguments = new Object[]{name, newValue};
             method = genericSetMethod;
         }
 
         //----------------------------------------------------------------------
-        // executing the setter method
+        // executing the method
         //----------------------------------------------------------------------
         if (method != null) {
             if (arguments.length == 1) {
@@ -2920,21 +2920,20 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
                 arguments[1] = newValue;
             }
 
-            MetaMethod transformedMetaMethod = VM_PLUGIN.transformMetaMethod(this, method);
-            transformedMetaMethod.doMethodInvoke(object, arguments);
+            VM_PLUGIN.transformMetaMethod(this, method).doMethodInvoke(object, arguments);
             return;
         }
 
-        //----------------------------------------------------------------------
-        // turn setProperty on a Map to put on the Map itself
-        //----------------------------------------------------------------------
-        if (method == null && !isStatic && this.isMap) {
+        //------------------------------------------------------------------
+        // java.util.Map put method
+        //------------------------------------------------------------------
+        if (isMap && !isStatic) {
             ((Map) object).put(name, newValue);
             return;
         }
 
         //----------------------------------------------------------------------
-        // error due to missing method/field
+        // missing property protocol
         //----------------------------------------------------------------------
         if (ambiguousListener) {
             throw new GroovyRuntimeException("There are multiple listeners for the property " + name + ". Please do not use the bean short form to access this listener.");
@@ -2942,7 +2941,6 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         if (mp != null) {
             throw new ReadOnlyPropertyException(name, theClass);
         }
-
         if ((isStatic || object instanceof Class) && !"metaClass".equals(name)) {
             invokeStaticMissingProperty(object, name, newValue, false);
         } else {
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
index f6d53cc..ce65d11 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
@@ -177,15 +177,17 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter {
             return;
         }
 
-        boolean isStaticProperty = receiver instanceof ClassExpression
-                && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType()));
+        if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
+
+        boolean isStaticProperty = (receiver instanceof ClassExpression
+                && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType())));
 
-        if (!isStaticProperty && isOrImplements(receiverType, MAP_TYPE)) {
-            // for maps, replace map.foo with map.get('foo')
+        // for maps, replace "map.foo" with "map.get('foo')" -- if no public field "foo" is declared (GROOVY-5001)
+        if (!isStaticProperty && isOrImplements(receiverType, MAP_TYPE)
+                && !java.util.Optional.ofNullable(getField(receiverType, propertyName)).filter(FieldNode::isPublic).isPresent()) {
             writeMapDotProperty(receiver, propertyName, safe);
             return;
         }
-        if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
         if (makeGetField(receiver, receiverType, propertyName, safe, implicitThis)) return;
         if (receiver instanceof ClassExpression) {
             if (makeGetField(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
diff --git a/src/main/java/org/codehaus/groovy/runtime/callsite/GetEffectivePojoPropertySite.java b/src/main/java/org/codehaus/groovy/runtime/callsite/GetEffectivePojoPropertySite.java
index 8eb022a..da2f73d 100644
--- a/src/main/java/org/codehaus/groovy/runtime/callsite/GetEffectivePojoPropertySite.java
+++ b/src/main/java/org/codehaus/groovy/runtime/callsite/GetEffectivePojoPropertySite.java
@@ -29,30 +29,18 @@ public class GetEffectivePojoPropertySite extends AbstractCallSite {
     private final MetaProperty effective;
     private final int version;
 
-    public GetEffectivePojoPropertySite(CallSite site, MetaClassImpl metaClass, MetaProperty effective) {
+    public GetEffectivePojoPropertySite(final CallSite site, final MetaClassImpl metaClass, final MetaProperty effective) {
         super(site);
         this.metaClass = metaClass;
         this.effective = effective;
         version = metaClass.getVersion();
     }
 
-//    public final Object callGetProperty (Object receiver) throws Throwable {
-//        if (GroovyCategorySupport.hasCategoryInCurrentThread() || receiver.getClass() != metaClass.getTheClass()) {
-//            return createGetPropertySite(receiver).getProperty(receiver);
-//        } else {
-//            try {
-//                return effective.getProperty(receiver);
-//            } catch (GroovyRuntimeException gre) {
-//                throw ScriptBytecodeAdapter.unwrap(gre);
-//            }
-//        }
-//    }
-
     @Override
-    public final CallSite acceptGetProperty(Object receiver) {
-//        if (GroovyCategorySupport.hasCategoryInCurrentThread() || !(receiver instanceof GroovyObject) || ((GroovyObject)receiver).getMetaClass() != metaClass) {
-        if (GroovyCategorySupport.hasCategoryInCurrentThread() || receiver==null || receiver.getClass() != metaClass.getTheClass()
-            || version != metaClass.getVersion()) { // metaClass is invalid
+    public final CallSite acceptGetProperty(final Object receiver) {
+        if (receiver == null || receiver.getClass() != metaClass.getTheClass()
+                || version != metaClass.getVersion() // metaClass is invalid
+                || GroovyCategorySupport.hasCategoryInCurrentThread()) {
             return createGetPropertySite(receiver);
         } else {
             return this;
diff --git a/src/test/groovy/MapTest.groovy b/src/test/groovy/MapTest.groovy
index 6f4aa7a..4738099 100644
--- a/src/test/groovy/MapTest.groovy
+++ b/src/test/groovy/MapTest.groovy
@@ -20,10 +20,9 @@ package groovy
 
 import groovy.test.GroovyTestCase
 
-class MapTest extends GroovyTestCase {
+final class MapTest extends GroovyTestCase {
 
     void testMap() {
-
         def m = [1:'one', '2':'two', 3:'three']
 
         assert m.size() == 3
@@ -55,22 +54,22 @@ class MapTest extends GroovyTestCase {
 
         assert m.size() == 2
 
-        assert m.containsKey("cheese")
-        assert m.containsValue("cheddar")
+        assert m.containsKey('cheese')
+        assert m.containsValue('cheddar')
 
 
-        if ( m.containsKey("cheese") ) {
+        if ( m.containsKey('cheese') ) {
             // ignore
         }
         else {
-            assert false , "should contain cheese!"
+            assert false , 'should contain cheese!'
         }
 
         if ( m.containsKey(3) ) {
             // ignore
         }
         else {
-            assert false , "should contain 3!"
+            assert false , 'should contain 3!'
         }
     }
 
@@ -78,12 +77,62 @@ class MapTest extends GroovyTestCase {
         def m = [:]
 
         assert m.size() == 0
-        assert !m.containsKey("cheese")
+        assert !m.containsKey('cheese')
 
-        m.put("cheese", "cheddar")
+        m.put('cheese', 'cheddar')
 
         assert m.size() == 1
-        assert m.containsKey("cheese")
+        assert m.containsKey('cheese')
+    }
+
+    /**
+     * Map "empty" property isn't Map#isEmpty.
+     */
+    void testMapEmpty() {
+        def m = [empty: 'no']
+
+        assert m.get('empty') == 'no'
+        assert m['empty'] == 'no'
+        assert m.empty == 'no'
+    }
+
+    /**
+     * Map "class" and "metaClass" properties aren't Object#getClass or GroovyObject#getMetaClass.
+     */
+    void testMapClass() {
+        def m = [class: 'xx', metaClass: 'yy']
+
+        assert m.get('class') == 'xx'
+        assert m['class'] == 'xx'
+        assert m.class == 'xx'
+        assert m.Class == null
+
+        assert m.get('metaClass') == 'yy'
+        assert m['metaClass'] == 'yy'
+        assert m.metaClass == 'yy'
+        assert m.MetaClass == null
+    }
+
+    // GROOVY-5001
+    void testMapDelegate() {
+        for (tag : ['','@TypeChecked','@CompileStatic']) {
+            assertScript """import groovy.transform.*
+                $tag class C {
+                    @Delegate Map m = [:]
+                    private def x = 'x'
+                    public  def y = 'y'
+                    def getZ() { 'z' }
+                }
+                def c = new C()
+
+                assert c.class == null
+                assert c.empty == null
+                assert c.m === c.@m
+                assert c.x == null
+                assert c.y == 'y'
+                assert c.z == 'z'
+            """
+        }
     }
 
     void testMapMutation() {
@@ -109,7 +158,34 @@ class MapTest extends GroovyTestCase {
         assert foo == 5
     }
 
-    void testMapLeftShift(){
+    // GROOVY-5001, GROOVY-5491
+    void testMapMutation2() {
+        for (tag : ['','@TypeChecked','@CompileStatic']) {
+            assertScript """import groovy.transform.*
+                    $tag class C extends HashMap { // just like GROOVY-662, GROOVY-8065, GROOVY-8074
+                    private boo
+                    def foo
+                }
+
+                def map = new C(foo:'bar')
+                assert map.@boo == null
+                assert map.boo  == null
+                assert map.foo == 'bar'
+
+                map.foo = 'baz' // set not put
+                assert map.foo == 'baz'
+                assert map.keySet().isEmpty()
+
+                map.boo = 'xx'
+                assert map.@boo == null
+                assert map.boo == 'xx'
+                assert map['boo'] == 'xx'
+                assert map.containsKey('boo')
+            """
+        }
+    }
+
+    void testMapLeftShift() {
         def map = [a:1, b:2]
         def other = [c:3]
         def entry = [d:4].iterator().toList()[0]
@@ -119,7 +195,7 @@ class MapTest extends GroovyTestCase {
         assert map == [a:1, b:2, c:3, d:4]
     }
 
-    void testFindAll(){
+    void testFindAll() {
         assert [a:1] == ['a':1, 'b':2].findAll {it.value == 1}
         assert [a:1] == ['a':1, 'b':2].findAll {it.key == 'a'}
         assert [a:1] == ['a':1, 'b':2].findAll {key,value -> key == 'a'}
@@ -208,7 +284,7 @@ class MapTest extends GroovyTestCase {
         assert map1 == [a:1, b:2]
     }
 
-    void testMapSort(){
+    void testMapSort() {
         def map = [a:100, c:20, b:3]
         def mapByValue = map.sort{ it.value }
         assert mapByValue.collect{ it.key } == ['b', 'c', 'a']
@@ -219,14 +295,14 @@ class MapTest extends GroovyTestCase {
     void testMapAdditionProducesCorrectValueAndPreservesOriginalMaps() {
         def left = [a:1, b:2]
         def right = [c:3]
-        assert left + right == [a:1, b:2, c:3], "should contain all entries from both maps"
-        assert left == [a:1, b:2] && right == [c:3], "LHS/RHS should not be modified"
+        assert left + right == [a:1, b:2, c:3], 'should contain all entries from both maps'
+        assert left == [a:1, b:2] && right == [c:3], 'LHS/RHS should not be modified'
     }
 
     void testMapAdditionGivesPrecedenceOfOverlappingValuesToRightMap() {
         def left = [a:1, b:1]
         def right = [a:2]
-        assert left + right == [a:2, b:1], "RHS should take precedence when entries have same key"
+        assert left + right == [a:2, b:1], 'RHS should take precedence when entries have same key'
     }
 
     void testMapAdditionPreservesOriginalTypeForCommonCases() {
@@ -246,11 +322,11 @@ class MapTest extends GroovyTestCase {
 
     void testTreeMapEach() {
         TreeMap map = [c:2, b:3, a:1]
-        String result1 = "", result2 = ""
+        String result1 = '', result2 = ''
         map.each{ k, v -> result1 += "$k$v " }
-        assert result1 == "a1 b3 c2 "
+        assert result1 == 'a1 b3 c2 '
         map.reverseEach{ e -> result2 += "$e.key$e.value " }
-        assert result2 == "c2 b3 a1 "
+        assert result2 == 'c2 b3 a1 '
     }
 
     void testMapWithDefault() {
@@ -264,8 +340,8 @@ class MapTest extends GroovyTestCase {
 
     void testMapIsCaseWithGrep() {
         def predicate = [apple:true, banana:true, lemon:false, orange:false, pear:true]
-        def fruitList = ["apple", "apple", "pear", "orange", "pear", "lemon", "banana"]
-        def expected = ["apple", "apple", "pear", "pear", "banana"]
+        def fruitList = ['apple', 'apple', 'pear', 'orange', 'pear', 'lemon', 'banana']
+        def expected = ['apple', 'apple', 'pear', 'pear', 'banana']
         assert fruitList.grep(predicate) == expected
     }
 
diff --git a/src/test/groovy/bugs/Groovy662.groovy b/src/test/groovy/bugs/Groovy662.groovy
new file mode 100644
index 0000000..5bc5ed0
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy662.groovy
@@ -0,0 +1,90 @@
+/*
+ *  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.
+ */
+package groovy.bugs
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy662 {
+
+    @Test
+    void testJavaClass() {
+        def object = new Groovy662_JavaClass()
+        assert object.getMyProperty() == 'Hello'
+        assert object.@myProperty == 'Hello'
+        assert object.myProperty == 'Hello'
+    }
+
+    @Test @CompileStatic
+    void testJavaClassCS() {
+        def object = new Groovy662_JavaClass()
+        assert object.getMyProperty() == 'Hello'
+        assert object.@myProperty == 'Hello'
+        assert object.myProperty == 'Hello'
+    }
+
+    @Test
+    void testJavaClassAsScript() {
+        assertScript '''
+            def object = new groovy.bugs.Groovy662_JavaClass()
+            assert object.getMyProperty() == 'Hello'
+            assert object.@myProperty == 'Hello'
+            assert object.myProperty == 'Hello'
+        '''
+    }
+
+    //
+
+    @Test
+    void testGroovyClass() {
+        def object = new Groovy662_GroovyClass()
+        assert object.getMyProperty() == 'Hello'
+        assert object.@myProperty == 'Hello'
+        assert object.myProperty == 'Hello'
+    }
+
+    @Test @CompileStatic
+    void testGroovyClassCS() {
+        def object = new Groovy662_GroovyClass()
+        assert object.getMyProperty() == 'Hello'
+        assert object.@myProperty == 'Hello'
+        assert object.myProperty == 'Hello'
+    }
+
+    @Test
+    void testGroovyClassAsScript() {
+        assertScript '''
+            def object = new groovy.bugs.Groovy662_GroovyClass()
+            assert object.getMyProperty() == 'Hello'
+            assert object.@myProperty == 'Hello'
+            assert object.myProperty == 'Hello'
+        '''
+    }
+}
+
+class Groovy662_GroovyClass extends HashMap {
+    @PackageScope String myProperty = 'Hello'
+
+    String getMyProperty() {
+        return myProperty
+    }
+}
diff --git a/src/test/groovy/bugs/Groovy662Bug.groovy b/src/test/groovy/bugs/Groovy662Bug.groovy
deleted file mode 100644
index ccf47cb..0000000
--- a/src/test/groovy/bugs/Groovy662Bug.groovy
+++ /dev/null
@@ -1,96 +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.
- */
-package groovy.bugs
-
-import groovy.test.GroovyTestCase
-
-//  The order of the classes is crucial, the first must be the GroovyTestCase.  Its name doesn't
-//  matter it just has to be first.
-
-/**
- * Test class and support to realize the GROOVY-662 test.  There is a difference between
- * improper uses of properties between Groovy defined classes and Java defined classes.  There
- * is no difference between correct uses so this is not a problem just an anti-regression test.
- */
-class Groovy662 extends GroovyTestCase {
-    private String expected = "Hello"
-
-    private usePropertyCorrectly(def object) { return object.@myProperty }
-
-    private usePropertyIncorrectly(def object) { return object.myProperty }
-
-    private useMethod(def object) { return object.getMyProperty() }
-
-    private void doAssertions(def object) {
-        assertTrue(useMethod(object) == expected)
-        assertTrue(usePropertyCorrectly(object) == expected)
-    }
-
-    private String theTestScriptDefinitions = """
-        String expected = "Hello"
-        def usePropertyCorrectly ( def object ) { return object.@myProperty }
-        def usePropertyIncorrectly ( def object ) { return object.myProperty }
-        def useMethod ( def object ) { return object.getMyProperty ( ) }
-    """
-
-    private String theTestScriptAssertions = """
-        assert useMethod ( object ) == expected
-        assert usePropertyCorrectly ( object ) == expected
-    """
-
-    public void testJavaClass() {
-        def object = new groovy.bugs.Groovy662_JavaClass()
-        doAssertions(object)
-        assertTrue(usePropertyIncorrectly(object) == null)
-    }
-
-    public void testGroovyClass() {
-        def object = new Groovy662_GroovyClass()
-        doAssertions(object)
-        assertTrue(usePropertyIncorrectly(object) == null)
-    }
-
-    public void testJavaClassAsScript() {
-        assertScript(theTestScriptDefinitions + """
-            def object = new groovy.bugs.Groovy662_JavaClass ( )
-        """ + theTestScriptAssertions + """
-            assert usePropertyIncorrectly ( object ) == null
-        """)
-    }
-
-    public void testGroovyClassAsScript() {
-        assertScript(theTestScriptDefinitions + """
-            class Groovy662_GroovyClass extends HashMap {
-                String myProperty = "Hello"
-                public String getMyProperty ( ) { return myProperty }
-            }
-            def object = new Groovy662_GroovyClass ( )
-        """ + theTestScriptAssertions + """
-            assert usePropertyIncorrectly ( object ) == null
-        """)
-    }
-}
-
-class Groovy662_GroovyClass extends HashMap {
-    String myProperty = "Hello"
-
-    public String getMyProperty() {
-        return myProperty
-    }
-}
diff --git a/src/test/groovy/bugs/Groovy8065Bug.groovy b/src/test/groovy/bugs/Groovy8065.groovy
similarity index 79%
rename from src/test/groovy/bugs/Groovy8065Bug.groovy
rename to src/test/groovy/bugs/Groovy8065.groovy
index be18ca3..c1b8543 100644
--- a/src/test/groovy/bugs/Groovy8065Bug.groovy
+++ b/src/test/groovy/bugs/Groovy8065.groovy
@@ -18,9 +18,13 @@
  */
 package groovy.bugs
 
-import groovy.test.GroovyTestCase
+import org.junit.Test
 
-class Groovy8065Bug extends GroovyTestCase {
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy8065 {
+
+    @Test
     void testMapWithCustomSetDuringAsTypeCast() {
         assertScript '''
             class MapWithSet extends LinkedHashMap {
@@ -33,18 +37,19 @@ class Groovy8065Bug extends GroovyTestCase {
         '''
     }
 
+    @Test
     void testMapWithPublicField() {
         assertScript '''
-            class A extends HashMap {
+            class C extends HashMap {
                 public foo
             }
-            def a = new A()
-            a.x = 1
-            assert a.x == 1
-            a.foo = 2
-            assert a.@foo == 2
-            assert a.foo == null
-            assert a == [x:1]
+            def c = new C()
+            c.x = 1
+            assert c.x == 1
+            c.foo = 2
+            assert c.@foo == 2
+            assert c.foo == 2
+            assert c == [x:1]
         '''
     }
 }
diff --git a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index 94f4ce4..0636c29 100644
--- a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -733,11 +733,24 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
 
     // GROOVY-6266
     void testMapKeyGenerics() {
-        assertScript """
-            HashMap<String,List<List>> AR=new HashMap<String,List<List>>()
-            AR.get('key',[['val1'],['val2']])
-            assert AR.'key'[0] == ['val1']
-        """
+        assertScript '''
+            HashMap<String,List<List>> map = new HashMap<String,List<List>>()
+            map.get('key',[['val1'],['val2']])
+            assert map.'key'[0] == ['val1']
+        '''
+    }
+
+    // GROOVY-8074
+    void testMapSubclassPropertyStyleAccess() {
+        assertScript '''
+            class MyMap extends LinkedHashMap {
+                def foo = 1
+            }
+
+            def map = new MyMap()
+            map.put('foo', 42)
+            assert map.foo == 1
+        '''
     }
 
     // GROOVY-6311
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/ArraysAndCollectionsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/ArraysAndCollectionsStaticCompileTest.groovy
index 038bcd4..b0ca167 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/ArraysAndCollectionsStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/ArraysAndCollectionsStaticCompileTest.groovy
@@ -120,19 +120,6 @@ class ArraysAndCollectionsStaticCompileTest extends ArraysAndCollectionsSTCTest
         assert astTrees['Foo'][1].count('DefaultGroovyMethods.toList') == 1
     }
 
-    // GROOVY-8074
-    void testMapSubclassPropertyStyleAccess() {
-        assertScript '''
-            class MyMap extends LinkedHashMap {
-                def foo = 1
-            }
-
-            def map = new MyMap()
-            map.put('foo', 42)
-            assert map.foo == 42
-        '''
-    }
-
     // GROOVY-10029
     void testCollectionToArrayAssignmentSC() {
         assertScript '''