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

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


The following commit(s) were added to refs/heads/GROOVY_5_0_X by this push:
     new 121105ccb9 GROOVY-11822: allow override `Object 
propertyMissing(String, Object)`
121105ccb9 is described below

commit 121105ccb9e17fc10d6831095f6b26c05cf77793
Author: Eric Milles <[email protected]>
AuthorDate: Sun Dec 28 15:41:32 2025 -0600

    GROOVY-11822: allow override `Object propertyMissing(String, Object)`
---
 src/main/java/org/codehaus/groovy/ast/ClassNode.java | 18 +++++++++++-------
 .../groovy/classgen/InnerClassCompletionVisitor.java | 16 ++++++++++++----
 src/test/groovy/gls/innerClass/InnerClassTest.groovy | 20 ++++++++++++++++++++
 3 files changed, 43 insertions(+), 11 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java 
b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index a5a789d4d4..96511c4b52 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -31,7 +31,6 @@ import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.ast.tools.ParameterUtils;
 import org.codehaus.groovy.control.CompilePhase;
-import org.codehaus.groovy.runtime.ArrayGroovyMethods;
 import org.codehaus.groovy.transform.ASTTransformation;
 import org.codehaus.groovy.transform.GroovyASTTransformation;
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
@@ -54,6 +53,7 @@ import java.util.stream.Stream;
 import static java.util.Objects.requireNonNull;
 import static java.util.stream.Collectors.toList;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock;
+import static org.codehaus.groovy.runtime.ArrayGroovyMethods.asBoolean;
 import static 
org.codehaus.groovy.transform.RecordTypeASTTransformation.recordNative;
 import static org.codehaus.groovy.transform.trait.Traits.isTrait;
 import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
@@ -911,10 +911,11 @@ public class ClassNode extends AnnotatedNode {
      * @return method node or null
      */
     public MethodNode getDeclaredMethod(String name, Parameter[] parameters) {
-        boolean zeroParameters = !ArrayGroovyMethods.asBoolean(parameters);
+        boolean zeroParameters = !asBoolean(parameters);
         for (MethodNode method : getDeclaredMethods(name)) {
-            if (zeroParameters ? method.getParameters().length == 0
-                    : parametersEqual(method.getParameters(), parameters)) {
+            Parameter[] methodParameters = method.getParameters();
+            if (zeroParameters ? methodParameters.length == 0
+                    : parametersEqual(methodParameters, parameters)) {
                 return method;
             }
         }
@@ -928,8 +929,11 @@ public class ClassNode extends AnnotatedNode {
      * @return method node or null
      */
     public MethodNode getMethod(String name, Parameter[] parameters) {
+        boolean zeroParameters = !asBoolean(parameters);
         for (MethodNode method : getMethods(name)) {
-            if (parametersEqual(method.getParameters(), parameters)) {
+            Parameter[] methodParameters = method.getParameters();
+            if (zeroParameters ? methodParameters.length == 0
+                    : parametersEqual(methodParameters, parameters)) {
                 return method;
             }
         }
@@ -1088,7 +1092,7 @@ public class ClassNode extends AnnotatedNode {
                 }
             }
             // GROOVY-11381:
-            if (getterMethod == null && 
ArrayGroovyMethods.asBoolean(getInterfaces())) {
+            if (getterMethod == null && asBoolean(getInterfaces())) {
                 for (ClassNode anInterface : getAllInterfaces()) {
                     MethodNode method = 
anInterface.getDeclaredMethod(getterName, Parameter.EMPTY_ARRAY);
                     if (method != null && method.isDefault() && 
(booleanReturnOnly ? ClassHelper.isPrimitiveBoolean(method.getReturnType()) : 
!method.isVoidMethod())) {
@@ -1189,7 +1193,7 @@ public class ClassNode extends AnnotatedNode {
             }
         }
 
-faces:  if (method == null && ArrayGroovyMethods.asBoolean(getInterfaces())) { 
// GROOVY-11323
+faces:  if (method == null && asBoolean(getInterfaces())) { // GROOVY-11323
             for (ClassNode cn : getAllInterfaces()) {
                 for (MethodNode mn : cn.getDeclaredMethods(name)) {
                     if (mn.isPublic() && !mn.isStatic() && 
hasCompatibleNumberOfArgs(mn, nArgs) && (nArgs == 0
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java 
b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
index 92186e9473..4c7ade06d9 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java
@@ -40,12 +40,14 @@ import org.codehaus.groovy.control.CompilationUnit;
 import org.codehaus.groovy.control.SourceUnit;
 import org.objectweb.asm.MethodVisitor;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.StringJoiner;
 import java.util.function.BiConsumer;
 
 import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.hasAnnotation;
 import static 
org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
+import static org.apache.groovy.ast.tools.ClassNodeUtils.getMethod;
 import static 
org.apache.groovy.ast.tools.ConstructorNodeUtils.getFirstIfSpecialConstructorCall;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock;
 import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
@@ -75,12 +77,12 @@ import static org.objectweb.asm.Opcodes.ACC_MANDATED;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
 import static org.objectweb.asm.Opcodes.ACC_STATIC;
 import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
 import static org.objectweb.asm.Opcodes.ALOAD;
 import static org.objectweb.asm.Opcodes.ARETURN;
 import static org.objectweb.asm.Opcodes.CHECKCAST;
 import static org.objectweb.asm.Opcodes.GETFIELD;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
-import static org.objectweb.asm.Opcodes.RETURN;
 
 public class InnerClassCompletionVisitor extends InnerClassVisitorHelper {
 
@@ -266,10 +268,15 @@ public class InnerClassCompletionVisitor extends 
InnerClassVisitorHelper {
                 }
         );
 
+        ClassNode[] nameValueTypes = {STRING_TYPE, OBJECT_TYPE};
+        MethodNode propertyMissing = getMethod(node, "propertyMissing", (m) -> 
!m.isStatic() && !m.isPrivate()
+                && 
Arrays.equals(Arrays.stream(m.getParameters()).map(Parameter::getType).toArray(),nameValueTypes));
+        ClassNode returnType = propertyMissing != null ? 
propertyMissing.getReturnType() : VOID_TYPE; // GROOVY-11822
+
         addMissingHandler(node,
                 "propertyMissing",
                 ACC_PUBLIC,
-                VOID_TYPE,
+                returnType,
                 params(param(STRING_TYPE, "name"), param(OBJECT_TYPE, 
"value")),
                 (methodBody, parameters) -> {
                     if (isStatic) {
@@ -283,7 +290,8 @@ public class InnerClassCompletionVisitor extends 
InnerClassVisitorHelper {
                                         mv.visitVarInsn(ALOAD, 1);
                                         mv.visitVarInsn(ALOAD, 2);
                                         mv.visitMethodInsn(INVOKEVIRTUAL, 
outerClassInternalName, "this$dist$set$" + outerClassDistance, 
"(Ljava/lang/String;Ljava/lang/Object;)V", false);
-                                        mv.visitInsn(RETURN);
+                                        if 
(!ClassHelper.isPrimitiveVoid(returnType)) mv.visitInsn(ACONST_NULL);
+                                        BytecodeHelper.doReturn(mv, 
returnType);
                                     }
                                 })
                         );
@@ -336,7 +344,7 @@ public class InnerClassCompletionVisitor extends 
InnerClassVisitorHelper {
         );
     }
 
-            void addMissingHandler(final InnerClassNode innerClass, final 
String methodName, final int modifiers,
+    /*   */ void addMissingHandler(final InnerClassNode innerClass, final 
String methodName, final int modifiers,
             final ClassNode returnType, final Parameter[] parameters, final 
BiConsumer<BlockStatement, Parameter[]> consumer) {
         MethodNode method = innerClass.getDeclaredMethod(methodName, 
parameters);
         if (method == null) {
diff --git a/src/test/groovy/gls/innerClass/InnerClassTest.groovy 
b/src/test/groovy/gls/innerClass/InnerClassTest.groovy
index ed3645535d..a4dfd08bc7 100644
--- a/src/test/groovy/gls/innerClass/InnerClassTest.groovy
+++ b/src/test/groovy/gls/innerClass/InnerClassTest.groovy
@@ -23,6 +23,8 @@ import org.codehaus.groovy.control.CompilationFailedException
 import org.codehaus.groovy.control.CompilerConfiguration
 import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.params.ParameterizedTest
+import org.junit.jupiter.params.provider.ValueSource
 
 import static groovy.test.GroovyAssert.assertScript
 import static groovy.test.GroovyAssert.shouldFail
@@ -2219,6 +2221,24 @@ final class InnerClassTest {
         '''
     }
 
+    // GROOVY-11822
+    @ParameterizedTest
+    @ValueSource(strings=['void','Void','def','Object'])
+    void testNestedPropertyHandling4(String returnType) {
+        def err = shouldFail """
+            class Upper {
+                $returnType propertyMissing(String name, Object value) {
+                }
+            }
+            class Outer {
+                static class Inner extends Upper {
+                }
+            }
+            new Outer.Inner().missing = 42
+        """
+        assert err =~ /No such property: missing for class: Outer.Inner/
+    }
+
     // GROOVY-7312
     @Test
     void testInnerClassOfInterfaceIsStatic() {

Reply via email to