This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY-4737 in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 62858b5f98525e4b32b4cf08c50acd691db22c6d Author: Eric Milles <[email protected]> AuthorDate: Mon Feb 2 13:52:24 2026 -0600 next step 7024 8050 --- src/main/java/groovy/lang/MetaClassImpl.java | 35 ++- .../classgen/InnerClassCompletionVisitor.java | 237 +-------------------- .../groovy/classgen/InnerClassVisitorHelper.java | 57 +---- .../stc/GroovyTypeCheckingExtensionSupport.java | 31 ++- .../groovy/gls/innerClass/InnerClassTest.groovy | 106 +++++---- .../groovy/typecheckers/FormatStringChecker.groovy | 16 +- .../groovy/groovy/typecheckers/RegexChecker.groovy | 55 ++--- .../groovy/typecheckers/CheckingVisitor.groovy | 23 +- 8 files changed, 165 insertions(+), 395 deletions(-) diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index 7f88b8314d..23e49d0a34 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -1232,8 +1232,9 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { var outerClass = sender.getEnclosingClass(); // check outer class nesting of call site if (outerClass != null && (sender == theClass || sender.isAssignableFrom(theClass))) { MetaClass omc = registry.getMetaClass(outerClass); + Object target = getOuterReference(sender, object); try { - return omc.invokeMethod(outerClass, outerClass, methodName, arguments, false, false); + return omc.invokeMethod(outerClass, target, methodName, arguments, false, false); } catch (MissingMethodException e) { mme.addSuppressed(e); } @@ -1242,6 +1243,32 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { throw mme; } + private Object getOuterReference(final Class<?> innerClass, final Object object) { + Object outer = null; + // non-static inner class may have outer class reference available in this$0 field + if (!(object instanceof Class) && (innerClass.getModifiers() & Opcodes.ACC_STATIC) == 0) { + try { + innerClass.getDeclaredField("this$0"); + outer = getAttribute(object,"this$0"); + if (outer instanceof GeneratedClosure) { + outer = ((Closure<?>) outer).getThisObject(); // skip closure(s) + } + } catch (NoSuchFieldException ignored) { + // an AIC is non-static but may not have this$0 + } catch (Throwable t) { + throw new GroovyRuntimeException(t); + } + } + if (outer == null) { + Class<?> outerClass = innerClass.getEnclosingClass(); + while (GeneratedClosure.class.isAssignableFrom(outerClass)) { + outerClass = outerClass.getEnclosingClass(); // skip closure(s) + } + outer = outerClass; + } + return outer; + } + private MetaMethod getMetaMethod(final Class<?> sender, final Object object, final String methodName, final boolean isCallToSuper, final Object[] arguments) { MetaMethod method = null; if (CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) { @@ -1951,8 +1978,9 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { var outerClass = sender.getEnclosingClass(); // check outer class nesting of call site if (outerClass != null && (sender == theClass || sender.isAssignableFrom(theClass))) { MetaClass omc = registry.getMetaClass(outerClass); + Object target = getOuterReference(sender, object); try { - return omc.getProperty(outerClass, outerClass, name, false, false); + return omc.getProperty(outerClass, target, name, false, false); } catch (MissingPropertyException e) { mpe.addSuppressed(e); } @@ -2847,8 +2875,9 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass { var outerClass = sender.getEnclosingClass(); // check outer class nesting of call site if (outerClass != null && (sender == theClass || sender.isAssignableFrom(theClass))) { MetaClass omc = registry.getMetaClass(outerClass); + Object target = getOuterReference(sender, object); try { - omc.setProperty(outerClass, outerClass, name, newValue, false, false); + omc.setProperty(outerClass, target, name, newValue, false, false); return; } catch (MissingPropertyException e) { mpe.addSuppressed(e); diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java index 8706be493b..399a82d438 100644 --- a/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassCompletionVisitor.java @@ -18,56 +18,31 @@ */ package org.codehaus.groovy.classgen; -import groovy.transform.CompileStatic; -import groovy.transform.stc.POJO; -import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.InnerClassNode; -import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; -import org.codehaus.groovy.ast.stmt.TryCatchStatement; -import org.codehaus.groovy.classgen.asm.BytecodeHelper; 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; -import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; -import static org.codehaus.groovy.ast.tools.GeneralUtils.args; import static org.codehaus.groovy.ast.tools.GeneralUtils.block; -import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.catchS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorSuperX; import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorThisX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX; import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.param; -import static org.codehaus.groovy.ast.tools.GeneralUtils.params; import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; -import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.tryCatchS; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.transform.trait.Traits.isTrait; import static org.objectweb.asm.Opcodes.ACC_FINAL; @@ -75,12 +50,6 @@ 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; public class InnerClassCompletionVisitor extends InnerClassVisitorHelper { @@ -104,25 +73,13 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper { if (node.isEnum() || node.isInterface() || isTrait(node.getOuterClass())) return; - // if the class has an inner class, add methods to support private member access - if (node.getInnerClasses().hasNext()) { - addDispatcherMethods(node); - } - if (node instanceof InnerClassNode innerClass) { thisField = node.getDeclaredField("this$0"); if (innerClass.getVariableScope() == null && node.getDeclaredConstructors().isEmpty()) { // add empty default constructor addGeneratedConstructor(innerClass, ACC_PUBLIC, Parameter.EMPTY_ARRAY, null, null); } - super.visitClass(node); - - boolean innerPojo = hasAnnotation(node, new ClassNode(POJO.class)) - && hasAnnotation(node, new ClassNode(CompileStatic.class)); - if (!innerPojo && !isStatic(innerClass)) { - addMopMethods(innerClass); - } } } @@ -156,195 +113,7 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper { } } - private static String getTypeDescriptor(ClassNode node, boolean isStatic) { - return BytecodeHelper.getTypeDescription(getClassNode(node, isStatic)); - } - - private static String getInternalName(ClassNode node, boolean isStatic) { - return BytecodeHelper.getClassInternalName(getClassNode(node, isStatic)); - } - - private static void addDispatcherMethods(ClassNode classNode) { - final int objectDistance = getObjectDistance(classNode); - - // since we added an anonymous inner class we should also - // add the dispatcher methods - - // add method dispatcher - BlockStatement block = new BlockStatement(); - MethodNode method = classNode.addSyntheticMethod( - "this$dist$invoke$" + objectDistance, - ACC_PUBLIC, - OBJECT_TYPE, - params(param(STRING_TYPE, "name"), param(OBJECT_TYPE, "args")), - ClassNode.EMPTY_ARRAY, - block - ); - setMethodDispatcherCode(block, VariableExpression.THIS_EXPRESSION, method.getParameters()); - - // add property setter - block = new BlockStatement(); - method = classNode.addSyntheticMethod( - "this$dist$set$" + objectDistance, - ACC_PUBLIC, - VOID_TYPE, - params(param(STRING_TYPE, "name"), param(OBJECT_TYPE, "value")), - ClassNode.EMPTY_ARRAY, - block - ); - setPropertySetterDispatcher(block, VariableExpression.THIS_EXPRESSION, method.getParameters()); - - // add property getter - block = new BlockStatement(); - method = classNode.addSyntheticMethod( - "this$dist$get$" + objectDistance, - ACC_PUBLIC, - OBJECT_TYPE, - params(param(STRING_TYPE, "name")), - ClassNode.EMPTY_ARRAY, - block - ); - setPropertyGetterDispatcher(block, VariableExpression.THIS_EXPRESSION, method.getParameters()); - } - - private void getThis(final MethodVisitor mv, final String classInternalName, final String outerClassDescriptor, final String innerClassInternalName) { - mv.visitVarInsn(ALOAD, 0); - if (thisField != null && CLOSURE_TYPE.equals(thisField.getType())) { - mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", BytecodeHelper.getTypeDescription(CLOSURE_TYPE)); - mv.visitMethodInsn(INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(CLOSURE_TYPE), "getThisObject", "()Ljava/lang/Object;", false); - mv.visitTypeInsn(CHECKCAST, innerClassInternalName); - } else { - mv.visitFieldInsn(GETFIELD, classInternalName, "this$0", outerClassDescriptor); - } - } - - private void addMopMethods(final InnerClassNode node) { - final boolean isStatic = isStatic(node); - final ClassNode outerClass = node.getOuterClass(); - final int outerClassDistance = getObjectDistance(outerClass); - final String classInternalName = BytecodeHelper.getClassInternalName(node); - final String outerClassInternalName = getInternalName(outerClass, isStatic); - final String outerClassDescriptor = getTypeDescriptor(outerClass, isStatic); - - addMissingHandler(node, - "methodMissing", - ACC_PUBLIC, - OBJECT_TYPE, - params(param(STRING_TYPE, "name"), param(OBJECT_TYPE, "args")), - (methodBody, parameters) -> { - if (isStatic) { - setMethodDispatcherCode(methodBody, classX(outerClass), parameters); - } else { - methodBody.addStatement( - new BytecodeSequence(new BytecodeInstruction() { - @Override - public void visit(final MethodVisitor mv) { - getThis(mv, classInternalName, outerClassDescriptor, outerClassInternalName); - mv.visitVarInsn(ALOAD, 1); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$invoke$" + outerClassDistance, "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - } - }) - ); - } - } - ); - - 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, - returnType, - params(param(STRING_TYPE, "name"), param(OBJECT_TYPE, "value")), - (methodBody, parameters) -> { - if (isStatic) { - setPropertySetterDispatcher(methodBody, classX(outerClass), parameters); - } else { - methodBody.addStatement( - new BytecodeSequence(new BytecodeInstruction() { - @Override - public void visit(final MethodVisitor mv) { - getThis(mv, classInternalName, outerClassDescriptor, outerClassInternalName); - mv.visitVarInsn(ALOAD, 1); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$set$" + outerClassDistance, "(Ljava/lang/String;Ljava/lang/Object;)V", false); - if (!ClassHelper.isPrimitiveVoid(returnType)) mv.visitInsn(ACONST_NULL); - BytecodeHelper.doReturn(mv, returnType); - } - }) - ); - } - } - ); - - addMissingHandler(node, - "propertyMissing", - ACC_PUBLIC, - OBJECT_TYPE, - params(param(STRING_TYPE, "name")), - (methodBody, parameters) -> { - if (isStatic) { - setPropertyGetterDispatcher(methodBody, classX(outerClass), parameters); - } else { - methodBody.addStatement( - new BytecodeSequence(new BytecodeInstruction() { - @Override - public void visit(final MethodVisitor mv) { - getThis(mv, classInternalName, outerClassDescriptor, outerClassInternalName); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, outerClassInternalName, "this$dist$get$" + outerClassDistance, "(Ljava/lang/String;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - } - }) - ); - } - } - ); - } - - private 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) { - // try { - // <consumer dispatch> - // } catch (MissingMethodException notFound) { - // throw new MissingMethodException(notFound.method, this, notFound.arguments) - // } - Parameter catchParam = param(OBJECT_TYPE, "notFound"); // dummy type - ClassNode exceptionT; - Expression newException; - if (methodName.endsWith("methodMissing")) { - exceptionT = ClassHelper.make(groovy.lang.MissingMethodException.class); - newException = ctorX(exceptionT, args(callX(varX(catchParam),"getMethod"), callThisX("getClass"), callX(varX(catchParam),"getArguments"))); - } else { - exceptionT = ClassHelper.make(groovy.lang.MissingPropertyException.class); - newException = ctorX(exceptionT, args(callX(varX(catchParam),"getProperty"), callThisX("getClass"), callX(varX(catchParam),"getCause"))); - } - catchParam.setType(exceptionT); - catchParam.setOriginType(exceptionT); - BlockStatement handleMissing = block(); - consumer.accept(handleMissing, parameters); - TryCatchStatement tryCatch = tryCatchS(handleMissing); - tryCatch.addCatch(catchS(catchParam, throwS(newException))); - - innerClass.addSyntheticMethod(methodName, modifiers, returnType, parameters, ClassNode.EMPTY_ARRAY, tryCatch); - - // if there is a user-defined method, add compiler error and continue - } else if (isStatic(innerClass) && (method.getModifiers() & ACC_SYNTHETIC) == 0) { - addError("\"" + methodName + "\" implementations are not supported on static inner classes as " + - "a synthetic version of \"" + methodName + "\" is added during compilation for the purpose " + - "of outer class delegation.", - method); - } - } - - private void addThisReference(ConstructorNode node) { + private void addThisReference(final ConstructorNode node) { if (!shouldHandleImplicitThisForInnerClass(classNode)) return; // add "this$0" field init @@ -396,7 +165,7 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper { node.setCode(block); } - private boolean shouldImplicitlyPassThisZero(ConstructorCallExpression cce) { + private boolean shouldImplicitlyPassThisZero(final ConstructorCallExpression cce) { boolean pass = false; if (cce.isThisCall()) { pass = true; @@ -415,7 +184,7 @@ public class InnerClassCompletionVisitor extends InnerClassVisitorHelper { return pass; } - private String getUniqueName(Parameter[] params, ConstructorNode node) { + private String getUniqueName(final Parameter[] params, final ConstructorNode node) { String namePrefix = "$p"; outer: for (int i = 0; i < 100; i++) { diff --git a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java index 32c5395164..ff1a9202da 100644 --- a/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java +++ b/src/main/java/org/codehaus/groovy/classgen/InnerClassVisitorHelper.java @@ -19,85 +19,30 @@ package org.codehaus.groovy.classgen; import org.codehaus.groovy.ast.ClassCodeVisitorSupport; -import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.expr.Expression; -import org.codehaus.groovy.ast.expr.SpreadExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.objectweb.asm.Opcodes; import static org.codehaus.groovy.ast.tools.GeneralUtils.assignS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.assignX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.callX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX; import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.indexX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.notX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; -import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; public abstract class InnerClassVisitorHelper extends ClassCodeVisitorSupport { - private static final ClassNode OBJECT_ARRAY = ClassHelper.OBJECT_TYPE.makeArray(); - protected static void addFieldInit(final Parameter p, final FieldNode fn, final BlockStatement block) { block.addStatement(assignS(fieldX(fn), varX(p))); } - protected static void setPropertyGetterDispatcher(final BlockStatement block, final Expression target, final Parameter[] parameters) { - block.addStatement(returnS(propX(target, varX(parameters[0])))); - } - - protected static void setPropertySetterDispatcher(final BlockStatement block, final Expression target, final Parameter[] parameters) { - block.addStatement(stmt(assignX(propX(target, varX(parameters[0])), varX(parameters[1])))); - } - - protected static void setMethodDispatcherCode (final BlockStatement block, final Expression target, final Parameter[] parameters) { - // if (!(args instanceof Object[])) return target.(name)(args) - block.addStatement(ifS( - notX(isInstanceOfX(varX(parameters[1]), OBJECT_ARRAY)), - returnS(callX(target, varX(parameters[0]), varX(parameters[1]))))); - - // if (((Object[])args).length == 1) return target.(name)(args[0]) - block.addStatement(ifS( - eqX(propX(castX(OBJECT_ARRAY, varX(parameters[1])), "length"), constX(1, true)), - returnS(callX(target, varX(parameters[0]), indexX(castX(OBJECT_ARRAY, varX(parameters[1])), constX(0, true)))))); - - // return target.(name)(*args) - block.addStatement(returnS(callX(target, varX(parameters[0]), new SpreadExpression(varX(parameters[1]))))); - } - - //-------------------------------------------------------------------------- - - protected static ClassNode getClassNode(final ClassNode cn, final boolean isStatic) { - return isStatic ? ClassHelper.CLASS_Type : cn; // TODO: Set class type parameter? - } - - protected static int getObjectDistance(ClassNode cn) { - int count = 0; - while (cn != null && !ClassHelper.isObjectType(cn)) { - cn = cn.getSuperClass(); - count += 1; - } - return count; - } - protected static boolean isStatic(final InnerClassNode cn) { return cn.getDeclaredField("this$0") == null; } protected static boolean shouldHandleImplicitThisForInnerClass(final ClassNode cn) { final int explicitOrImplicitStatic = Opcodes.ACC_ENUM | Opcodes.ACC_INTERFACE | Opcodes.ACC_RECORD | Opcodes.ACC_STATIC; - return (cn.getModifiers() & explicitOrImplicitStatic) == 0 && (cn instanceof InnerClassNode && !((InnerClassNode) cn).isAnonymous()) + return (cn.getModifiers() & explicitOrImplicitStatic) == 0 && (cn instanceof InnerClassNode inner && !inner.isAnonymous()) && cn.getAnnotations().stream().noneMatch(aNode -> "groovy.transform.RecordType".equals(aNode.getClassNode().getName())); // GROOVY-11600 } } diff --git a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java index 90f204f79e..e837def17d 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/GroovyTypeCheckingExtensionSupport.java @@ -488,47 +488,46 @@ public class GroovyTypeCheckingExtensionSupport extends AbstractTypeCheckingExte * @see <a href="https://docs.groovy-lang.org/latest/html/documentation/#_a_dsl_for_type_checking">Groovy Language Documentation</a> */ public abstract static class TypeCheckingDSL extends Script { + private GroovyTypeCheckingExtensionSupport extension; @Override - public Object getProperty(final String property) { + public Object getProperty(final String name) { try { - return InvokerHelper.getProperty(extension, property); + return InvokerHelper.getProperty(extension, name); } catch (Exception e) { - return super.getProperty(property); + return super.getProperty(name); } } @Override - public void setProperty(final String property, final Object newValue) { + public void setProperty(final String name, final Object value) { try { - InvokerHelper.setProperty(extension, property, newValue); + InvokerHelper.setProperty(extension, name, value); } catch (Exception e) { - super.setProperty(property, newValue); + super.setProperty(name, value); } } - @Override - public Object invokeMethod(final String name, final Object args) { - if (name.startsWith("is") && name.endsWith("Expression") && args instanceof Object[] && ((Object[]) args).length == 1) { - String type = name.substring(2); - Object target = ((Object[]) args)[0]; + public Object methodMissing(final String name, final Object args) { + if (name.startsWith("is") && name.endsWith("Expression") && args instanceof Object[] array && array.length == 1) { + Object target = array[0]; if (target == null) return Boolean.FALSE; try { - Class<?> typeClass = Class.forName("org.codehaus.groovy.ast.expr." + type); - return typeClass.isAssignableFrom(target.getClass()); + Class<?> type = Class.forName("org.codehaus.groovy.ast.expr." + name.substring(2)); + return type.isAssignableFrom(target.getClass()); } catch (ClassNotFoundException e) { return Boolean.FALSE; } } - if (args instanceof Object[] argsArray && ((Object[]) args).length == 1 && ((Object[]) args)[0] instanceof Closure) { + if (args instanceof Object[] array && array.length == 1 && array[0] instanceof Closure closure) { String methodName = METHOD_ALIASES.get(name); if (methodName == null) { return InvokerHelper.invokeMethod(extension, name, args); } - List<Closure> closures = extension.eventHandlers.computeIfAbsent(methodName, k -> new LinkedList<Closure>()); - closures.add((Closure) argsArray[0]); + var closures = extension.eventHandlers.computeIfAbsent(methodName, k -> new LinkedList<>()); + closures.add(closure); return null; } else { return InvokerHelper.invokeMethod(extension, name, args); diff --git a/src/test/groovy/gls/innerClass/InnerClassTest.groovy b/src/test/groovy/gls/innerClass/InnerClassTest.groovy index 90f7f7a1ae..58fa45ab1b 100644 --- a/src/test/groovy/gls/innerClass/InnerClassTest.groovy +++ b/src/test/groovy/gls/innerClass/InnerClassTest.groovy @@ -594,14 +594,14 @@ final class InnerClassTest { @Test void testUsageOfOuterField() { assertScript ''' - interface Run { + interface A { def run() } - class Foo { + class C { private x = 1 def foo() { - def runner = new Run() { + def runner = new A() { def run() { return x } } runner.run() @@ -609,24 +609,25 @@ final class InnerClassTest { void x(y) { x = y } } - def foo = new Foo() - assert foo.foo() == 1 - foo.x(2) - assert foo.foo() == 2 + + def c = new C() + assert c.foo() == 1 + c.x(2) + assert c.foo() == 2 ''' } @Test void testUsageOfOuterField2() { assertScript ''' - interface Run { + interface A { def run() } - class Foo { + class C { private static x = 1 static foo() { - def runner = new Run() { + def runner = new A() { def run() { return x } } runner.run() @@ -634,33 +635,34 @@ final class InnerClassTest { static x(y) { x = y } } - assert Foo.foo() == 1 - Foo.x(2) - assert Foo.foo() == 2 + + assert C.foo() == 1 + C.x(2) + assert C.foo() == 2 ''' } @Test void testUsageOfOuterField3() { assertScript ''' - interface X { + interface A { def m() } - - class A { - def pm = "pm" - - def bar(x) {x().m()} + class C { + def bar(x) { + x().m() + } def foo() { bar { -> - return new X() { - def m() { pm } + return new A() { + def m() { p } } } } + final p = 'p' } - def a = new A() - assert "pm" == a.foo() + + assert new C().foo() == 'p' ''' } @@ -713,6 +715,7 @@ final class InnerClassTest { static x(y) { x = y } } + assert Foo.foo() == 1 Foo.x(2) assert Foo.foo() == 2 @@ -928,15 +931,16 @@ final class InnerClassTest { // GROOVY-8050 @Test void testUsageOfOuterField13() { - assertScript ''' + def err = shouldFail ''' class Outer { class Inner { } def p = 1 } - def i = new Outer.Inner(new Outer()) - assert i.p == 1 + + new Outer.Inner(new Outer()).p ''' + assert err =~ /MissingPropertyException: No such property: p for class: Outer.Inner/ } @NotYetImplemented @Test @@ -1063,28 +1067,27 @@ final class InnerClassTest { @Test void testUsageOfOuterFieldOverridden() { assertScript ''' - interface Run { + interface A { def run() } - class Foo { - private x = 1 - - def foo() { - def runner = new Run() { + class B { + def test() { + def runner = new A() { def run() { return x } // <-- dynamic variable } runner.run() } - + private x = 1 void setX(val) { x = val } } - class Bar extends Foo { - def x = 'string' // hides 'foo.@x' and overrides 'foo.setX(val)' + class C extends B { + def x = 'string' // hides 'B.@x' and overrides 'B.setX(val)' } - def bar = new Bar() - assert bar.foo() == 'string' - bar.x = 'new string' - assert bar.foo() == 'new string' + + def c = new C() + assert c.test() == 1 + c.x = 'new string' + assert c.test() == 1 ''' } @@ -2044,10 +2047,12 @@ final class InnerClassTest { @Test void testEnclosingMethodIsSet2() { assertScript ''' - import groovy.transform.ASTTest + import groovy.transform.* import org.codehaus.groovy.ast.expr.* import static org.codehaus.groovy.classgen.Verifier.* + @Field Object result + @ASTTest(phase=CLASS_GENERATION, value={ def init = node.parameters[0].getNodeMetaData(INITIAL_EXPRESSION) assert init instanceof MapExpression @@ -2410,9 +2415,28 @@ final class InnerClassTest { assert err =~ /No such property: missing for class: Outer.Inner/ } - // GROOVY-9618 + // GROOVY-11823 @Test void testNestedPropertyHandling5() { + assertScript ''' + class Upper { + Object propertyMissing(String name) { + if (name == 'fizz') return 'buzz' + throw new MissingPropertyException(name, getClass()) + } + } + class Outer { + static class Inner extends Upper { + } + } + def inner = new Outer.Inner() + assert inner.fizz == 'buzz' + ''' + } + + // GROOVY-9618 + @Test + void testNestedPropertyHandling6() { assertScript ''' class Super { public static X = 1 diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy index 893cab1ae0..a93af52c30 100644 --- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy +++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/FormatStringChecker.groovy @@ -68,8 +68,10 @@ import static org.codehaus.groovy.ast.ClassHelper.makeCached */ @Incubating class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { + private static final ClassNode LOCALE_TYPE = makeCached(Locale) private static final ClassNode FORMATTER_TYPE = makeCached(Formatter) + private String formatSpecifier = /%(\d+\$)?([-#+ 0,(\<]*)?(\d+)?(\.\d+)?([tT])?([a-zA-Z%])/ private List<ASTNode> formatMethods = [ macro(CompilePhase.SEMANTIC_ANALYSIS) { String.format(a) }.withConstraints { varargPlaceholder a }, @@ -95,13 +97,13 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin makeVisitor().checkFormatterMethod(call) } } + afterVisitMethod { MethodNode method -> - def visitor = makeVisitor() - method.code.visit(visitor) + method.code.visit(makeVisitor()) } } - private makeVisitor() { + private CheckingVisitor makeVisitor() { new CheckingVisitor() { @Override void visitMethodCallExpression(MethodCallExpression call) { @@ -145,7 +147,7 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin } } - void checkFormatStringTypes(Expression expression, List<Expression> args, Expression target) { + private void checkFormatStringTypes(Expression expression, List<Expression> args, Expression target) { int next = 0 int prevIndex = -1 expression.value.eachMatch(formatSpecifier) { spec -> @@ -254,14 +256,14 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin } } - void checkBadFlags(flagList, conversion, Expression target, String badFlags) { + private void checkBadFlags(flagList, conversion, Expression target, String badFlags) { def mismatched = flagList?.findAll { badFlags.contains(it) }?.join() if (mismatched) { addStaticTypeError("FormatFlagsConversionMismatch: Conversion = $conversion, Flags = '$mismatched'", target) } } - void checkFormatStringConstantArgs(ConstantExpression formatString, args, Expression target) { + private void checkFormatStringConstantArgs(ConstantExpression formatString, args, Expression target) { try { new Formatter().format(formatString.value, *args) } catch (IllegalFormatException ex) { @@ -275,7 +277,7 @@ class FormatStringChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckin void visitDeclarationExpression(DeclarationExpression decl) { super.visitDeclarationExpression(decl) if (decl.variableExpression != null) { - if (isConstantExpression(decl.rightExpression)) { + if (decl.rightExpression instanceof ConstantExpression) { localConstVars.put(decl.variableExpression, decl.rightExpression) } } diff --git a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy index 09e6e6a16d..f53ef4f3ed 100644 --- a/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy +++ b/subprojects/groovy-typecheckers/src/main/groovy/groovy/typecheckers/RegexChecker.groovy @@ -25,12 +25,14 @@ import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.expr.BinaryExpression import org.codehaus.groovy.ast.expr.BitwiseNegationExpression +import org.codehaus.groovy.ast.expr.ClassExpression import org.codehaus.groovy.ast.expr.ConstantExpression import org.codehaus.groovy.ast.expr.DeclarationExpression import org.codehaus.groovy.ast.expr.Expression import org.codehaus.groovy.ast.expr.MethodCall import org.codehaus.groovy.ast.expr.MethodCallExpression import org.codehaus.groovy.ast.expr.StaticMethodCallExpression +import org.codehaus.groovy.ast.expr.VariableExpression import org.codehaus.groovy.syntax.Types import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport import org.codehaus.groovy.transform.stc.StaticTypesMarker @@ -100,14 +102,15 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.checkC */ @Incubating class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { - private static final String REGEX_GROUP_COUNT = RegexChecker.simpleName + '_INFERRED_GROUP_COUNT' - private static final String REGEX_MATCHER_RESULT_TYPE = RegexChecker.simpleName + '_MATCHER_RESULT_INFERRED_TYPE' + private static final ClassNode MATCHER_TYPE = ClassHelper.make(Matcher) + private static final String REGEX_GROUP_COUNT = RegexChecker.getSimpleName() + '_INFERRED_GROUP_COUNT' + private static final String REGEX_MATCHER_RESULT_TYPE = RegexChecker.getSimpleName() + '_MATCHER_RESULT_INFERRED_TYPE' @Override Object run() { beforeVisitMethod { MethodNode method -> - def visitor = new CheckingVisitor() { + method.code.visit(new CheckingVisitor() { @Override void visitBitwiseNegationExpression(BitwiseNegationExpression expression) { super.visitBitwiseNegationExpression(expression) @@ -122,7 +125,7 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { def exp = findConstExp(expression.rightExpression, String) checkRegex(exp, expression) } else if (expression.operation.type == Types.LEFT_SQUARE_BRACKET) { - if (isVariableExpression(expression.leftExpression)) { + if (expression.leftExpression instanceof VariableExpression) { def var = findTargetVariable(expression.leftExpression) def groupCount = var?.getNodeMetaData(REGEX_GROUP_COUNT) if (groupCount != null) { @@ -140,7 +143,7 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { @Override void visitMethodCallExpression(MethodCallExpression call) { super.visitMethodCallExpression(call) - if (isClassExpression(call.objectExpression)) { + if (call.objectExpression instanceof ClassExpression) { checkPatternMethod(call, call.objectExpression.type) } else if (isPattern(call.receiver) && call.methodAsString == 'matcher') { def var = findTargetVariable(call.receiver) @@ -169,7 +172,7 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { void visitDeclarationExpression(DeclarationExpression decl) { super.visitDeclarationExpression(decl) if (decl.variableExpression != null) { - if (isConstantExpression(decl.rightExpression)) { + if (decl.rightExpression instanceof ConstantExpression) { localConstVars.put(decl.variableExpression, decl.rightExpression) } def groupCount = decl.rightExpression.getNodeMetaData(REGEX_GROUP_COUNT) @@ -178,15 +181,13 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { } } } - - } - method.code.visit(visitor) + }) } incompatibleAssignment { lhsType, rhsType, expr -> - if (isBinaryExpression(expr) && isAssignment(expr.operation.type)) { + if (expr instanceof BinaryExpression && isAssignment(expr.operation.type)) { def from = expr.rightExpression - if (isBinaryExpression(from) && from.operation.type == Types.LEFT_SQUARE_BRACKET && getType(from.leftExpression) == MATCHER_TYPE) { + if (from instanceof BinaryExpression && from.operation.type == Types.LEFT_SQUARE_BRACKET && getType(from.leftExpression) == MATCHER_TYPE) { ClassNode inferred = from.getNodeMetaData(REGEX_MATCHER_RESULT_TYPE) if (inferred) { handled = true @@ -200,9 +201,9 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { } } - methodNotFound { receiverType, name, argList, argTypes, call -> + methodNotFound { receiverType, name, argList, argTypes, MethodCall call -> def receiver = call.receiver - if (isBinaryExpression(receiver) && receiver.operation.type == Types.LEFT_SQUARE_BRACKET && getType(receiver.leftExpression) == MATCHER_TYPE) { + if (receiver instanceof BinaryExpression && receiver.operation.type == Types.LEFT_SQUARE_BRACKET && getType(receiver.leftExpression) == MATCHER_TYPE) { ClassNode inferred = receiver.getNodeMetaData(REGEX_MATCHER_RESULT_TYPE) if (inferred) { makeDynamic(call, inferred) @@ -211,12 +212,12 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { } afterVisitMethod { MethodNode method -> - def visitor = new CheckingVisitor() { + method.code.visit(new CheckingVisitor() { @Override void visitDeclarationExpression(DeclarationExpression decl) { super.visitDeclarationExpression(decl) if (decl.variableExpression != null) { - if (isConstantExpression(decl.rightExpression)) { + if (decl.rightExpression instanceof ConstantExpression) { localConstVars.put(decl.variableExpression, decl.rightExpression) } def groupCount = decl.rightExpression.getNodeMetaData(REGEX_GROUP_COUNT) @@ -236,7 +237,7 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { } } super.visitMethodCallExpression(call) - if (isVariableExpression(call.objectExpression) && call.methodAsString == 'group' && isMatcher(call.receiver) && call.arguments.expressions) { + if (call.objectExpression instanceof VariableExpression && call.methodAsString == 'group' && isMatcher(call.receiver) && call.arguments.expressions) { def var = findTargetVariable(call.receiver) def maxCnt = var?.getNodeMetaData(REGEX_GROUP_COUNT) if (maxCnt != null) { @@ -261,21 +262,22 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { } } } - } - method.code.visit(visitor) + }) } } - private boolean isMatcher(Expression obj) { - obj.type == MATCHER_TYPE || - obj.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE) == MATCHER_TYPE || - obj.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE) == MATCHER_TYPE + //-------------------------------------------------------------------------- + + private boolean isMatcher(Expression exp) { + exp.type == MATCHER_TYPE + || exp.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE) == MATCHER_TYPE + || exp.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE) == MATCHER_TYPE } - private boolean isPattern(Expression obj) { - obj.type == PATTERN_TYPE || - obj.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE) == PATTERN_TYPE || - obj.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE) == PATTERN_TYPE + private boolean isPattern(Expression exp) { + exp.type == PATTERN_TYPE + || exp.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE) == PATTERN_TYPE + || exp.getNodeMetaData(StaticTypesMarker.INFERRED_RETURN_TYPE) == PATTERN_TYPE } private void checkRegex(ConstantExpression regex, Expression target) { @@ -290,5 +292,4 @@ class RegexChecker extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { addStaticTypeError('Bad regex' + additional + ex.message, target) } } - } diff --git a/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy b/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy index 1bd63804b5..f7b285fc1d 100644 --- a/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy +++ b/subprojects/groovy-typecheckers/src/main/groovy/org/apache/groovy/typecheckers/CheckingVisitor.groovy @@ -18,6 +18,7 @@ */ package org.apache.groovy.typecheckers +import groovy.transform.AutoFinal import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassCodeVisitorSupport import org.codehaus.groovy.ast.FieldNode @@ -27,10 +28,15 @@ import org.codehaus.groovy.ast.expr.Expression import org.codehaus.groovy.ast.expr.VariableExpression import org.codehaus.groovy.control.SourceUnit -@CompileStatic -@SuppressWarnings('Instanceof') +@AutoFinal @CompileStatic class CheckingVisitor extends ClassCodeVisitorSupport { - protected final Map<Expression, Expression> localConstVars = new HashMap<>() + + @Override + protected SourceUnit getSourceUnit() { + null + } + + protected final Map<Expression,Expression> localConstVars = [:] protected Expression findConstExp(Expression exp, Class type) { if (exp instanceof ConstantExpression && type.isAssignableFrom(exp.value.getClass())) { @@ -48,16 +54,11 @@ class CheckingVisitor extends ClassCodeVisitorSupport { null } - @Override - protected SourceUnit getSourceUnit() { - null - } - - static Variable findTargetVariable(final VariableExpression ve) { - Variable accessedVariable = ve.accessedVariable + protected Variable findTargetVariable(VariableExpression ve) { + def accessedVariable = ve.accessedVariable if (accessedVariable != null && accessedVariable != ve) { if (accessedVariable instanceof VariableExpression) { - return findTargetVariable((VariableExpression) accessedVariable) + return findTargetVariable(accessedVariable) } return accessedVariable }
