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

davydotcom pushed a commit to branch feature/taglib-method-actions
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit f42e058c6edd0fe67119e5b45b059beaeefd7b8e
Author: David Estes <[email protected]>
AuthorDate: Wed Feb 25 21:59:21 2026 -0500

    Bind Map tag method args by name except reserved attrs map
    
    - treat only Map parameter named attrs as full tag attributes map
    - allow other Map-typed parameters to bind from attribute key by parameter 
name
    - add regression tests for map-valued attribute binding and reserved attrs 
behavior
    
    Co-Authored-By: Oz <[email protected]>
---
 .../main/groovy/org/grails/taglib/TagMethodInvoker.java |  4 ++--
 .../grails/web/taglib/MethodDefinedTagLibSpec.groovy    | 17 +++++++++++++++++
 2 files changed, 19 insertions(+), 2 deletions(-)

diff --git 
a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
 
b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
index 5669a77512..cf19e2fb4b 100644
--- 
a/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
+++ 
b/grails-gsp/grails-taglib/src/main/groovy/org/grails/taglib/TagMethodInvoker.java
@@ -126,8 +126,9 @@ public final class TagMethodInvoker {
         Parameter[] parameters = method.getParameters();
         Object[] args = new Object[parameters.length];
         for (int i = 0; i < parameters.length; i++) {
+            String parameterName = parameters[i].getName();
             Class<?> parameterType = parameters[i].getType();
-            if (Map.class.isAssignableFrom(parameterType)) {
+            if (Map.class.isAssignableFrom(parameterType) && 
"attrs".equals(parameterName)) {
                 args[i] = attrs;
                 continue;
             }
@@ -135,7 +136,6 @@ public final class TagMethodInvoker {
                 args[i] = body != null ? body : TagOutput.EMPTY_BODY_CLOSURE;
                 continue;
             }
-            String parameterName = parameters[i].getName();
             Object value = attrs != null ? attrs.get(parameterName) : null;
             if (value == null && parameters.length == 1 && attrs != null && 
attrs.size() == 1) {
                 value = attrs.values().iterator().next();
diff --git 
a/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy
 
b/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy
index ae636b4996..3deeb6171c 100644
--- 
a/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy
+++ 
b/grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/MethodDefinedTagLibSpec.groovy
@@ -43,6 +43,15 @@ class MethodDefinedTagLibSpec extends Specification 
implements TagLibUnitTest<Me
         applyTemplate('<g:multiTypedTag first="hello" second="world" />') == 
'hello-world'
     }
 
+    void "method tag can bind map-valued attribute to map-typed argument by 
parameter name"() {
+        expect:
+        applyTemplate('<g:mapValueTag config="${[k:\'v\']}" />') == 'v'
+    }
+    void "method tag still supports reserved attrs map parameter"() {
+        expect:
+        applyTemplate('<g:attrsMapTag blah="duh" />') == 'duh'
+    }
+
     void "method tag can use implicit body closure"() {
         expect:
         applyTemplate('<g:bodyTag>abc</g:bodyTag>') == 'before-abc-after'
@@ -100,6 +109,14 @@ class MethodTagLib {
         out << "${first}-${second}"
     }
 
+    def mapValueTag(Map config) {
+        out << "${config.k}"
+    }
+
+    def attrsMapTag(Map attrs) {
+        out << "${attrs.blah}"
+    }
+
     def bodyTag() {
         out << "before-${body()}-after"
     }

Reply via email to