From dc01f8c48173b26b502909858699d13f5cba2f33 Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Sun, 31 May 2026 10:38:25 -0500 Subject: [PATCH] GROOVY-12024: add `getAt(Map,String)` and `putAt(Map,String,V)` --- .../groovy/runtime/DefaultGroovyMethods.java | 39 ++++++++++++++++++- .../stc/FieldsAndPropertiesSTCTest.groovy | 34 ++++++++-------- .../transform/stc/GenericsSTCTest.groovy | 6 +-- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index 40b633fe2a2..0b6e9d9920b 100644 --- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -7351,6 +7351,24 @@ public static V getAt(Map self, Object key) { return self.get(key); } + /** + * Support the subscript operator for a Map. + *
def map = [foo:'bar', class:'baz', empty:'nope']
+     * assert map['foo'] == 'bar'
+     * assert map['class'] == 'baz'
+     * assert map['empty'] == 'nope'
+     * assert map['properties'] == null // GROOVY-12024
+     * 
+ * + * @param self a Map + * @param key a String as a key for the map + * @return the value corresponding to the given key + * @since 6.0.0 + */ + public static V getAt(Map self, String key) { + return self.get(key); + } + /** * Support the subscript operator for a Bitset * @@ -12950,7 +12968,7 @@ public static void putAt(List self, List splice, Object value) { * @param self a Map * @param key an Object as a key for the map * @param value the value to put into the map - * @return the value corresponding to the given key + * @return the value * @since 1.0 */ public static V putAt(Map self, K key, V value) { @@ -12958,6 +12976,25 @@ public static V putAt(Map self, K key, V value) { return value; } + /** + * A helper method to allow maps to work with subscript operators. + *
def map = [:]
+     * assert map['properties'] == null
+     * map['properties'] = new Object() // GROOVY-12024
+     * assert map['properties'] != null
+     * 
+ * + * @param self a Map + * @param key an Object as a key for the map + * @param value the value to put into the map + * @return the value + * @since 6.0.0 + */ + public static V putAt(Map self, String key, V value) { + self.put(key, value); + return value; + } + /** * Support assigning a range of values with a single assignment statement. * diff --git a/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy b/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy index a5b2028430f..c197e2502b7 100644 --- a/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy @@ -765,7 +765,7 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { 'Cannot set read-only property: properties' } - // GROOVY-8074 + // GROOVY-8074, GROOVY-12024 @Test void testMapPropertyAccess6() { assertScript ''' @@ -775,23 +775,27 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { def map = new C() map.put('foo', 11) assert map.foo == 1 - assert map['foo'] == 1 + assert map['foo'] == 11 + assert map.get('foo') == 11 ''' assertScript """ def map = new ${MapType.name}() map.put('foo', 11) assert map.foo == 1 - assert map['foo'] == 1 + assert map['foo'] == 11 + assert map.get('foo') == 11 map.put('bar', 22) assert map.bar == 22 assert map['bar'] == 22 + assert map.get('bar') == 22 map.put('baz', 33) assert map.baz == 33 assert map['baz'] == 33 + assert map.get('baz') == 33 """ } - // GROOVY-5001, GROOVY-5491, GROOVY-6144 + // GROOVY-5001, GROOVY-5491, GROOVY-6144, GROOVY-12024 @Test void testMapPropertyAccess7() { String types = ''' @@ -804,23 +808,21 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { assertScript types + ''' def map = new C() map.put('a', new A()) + assert map.a != null + assert map.b != null + assert map['a'] != null + assert map['b'] == null assert map.get('a') != null assert map.get('b') == null - A a = map.a - B b = map.b - a = map['a'] - b = map['b'] - assert a instanceof A - assert b instanceof B ''' assertScript types + ''' def test(C map) { - A a = map.a - B b = map.b - a = map['a'] - b = map['b'] - assert a instanceof A - assert b instanceof B + assert map.a != null + assert map.b != null + assert map['a'] != null + assert map['b'] == null + assert map.get('a') != null + assert map.get('b') == null } test(new C().tap{ put('a', new A()) }) ''' diff --git a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy index 4c7f9b8625d..e2045aa1e9a 100644 --- a/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/GenericsSTCTest.groovy @@ -2575,7 +2575,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { def map = new HashMap() map['x'] = new Object() ''', - 'Cannot assign value of type java.lang.Object to variable of type java.lang.Integer' + 'Cannot call','putAt','with arguments [java.util.HashMap, java.lang.String, java.lang.Object]' } // GROOVY-9069 @@ -2591,8 +2591,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } } ''', - 'Cannot call java.util.Map#put(java.lang.String, java.util.Map>) with arguments [java.lang.String, java.util.LinkedHashMap>]', - 'Cannot assign java.util.LinkedHashMap> to: java.util.Map>' + 'Cannot call java.util.Map#put(java.lang.String, java.util.Map>) with arguments [java.lang.String, java.util.LinkedHashMap>]', + 'Cannot call org.codehaus.groovy.runtime.DefaultGroovyMethods#putAt(java.util.Map, java.lang.String, V) with arguments [java.util.Map>>, java.lang.String, java.util.LinkedHashMap>]' assertScript ''' void test(Map>> maps) {