This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch GROOVY_4_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit e103144ad76baac6a576a983b54cd3bc53b3cba9 Author: Paul King <[email protected]> AuthorDate: Wed Jan 19 15:04:35 2022 +1000 GROOVY-10434: ClassNode isSealed() refactoring: mark methods @Incubating, move sealed detection to an earlier phase and fix redirect issue --- src/main/java/groovy/transform/Sealed.java | 4 +- .../java/org/codehaus/groovy/ast/ClassNode.java | 8 ++- .../org/codehaus/groovy/classgen/Verifier.java | 38 +++------- .../SealedCompletionASTTransformation.java | 81 ++++++++++++++++++++++ 4 files changed, 99 insertions(+), 32 deletions(-) diff --git a/src/main/java/groovy/transform/Sealed.java b/src/main/java/groovy/transform/Sealed.java index fef0bff..3fa96a1 100644 --- a/src/main/java/groovy/transform/Sealed.java +++ b/src/main/java/groovy/transform/Sealed.java @@ -35,7 +35,9 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Incubating -@GroovyASTTransformationClass("org.codehaus.groovy.transform.SealedASTTransformation") +@GroovyASTTransformationClass({ + "org.codehaus.groovy.transform.SealedASTTransformation", + "org.codehaus.groovy.transform.SealedCompletionASTTransformation"}) public @interface Sealed { /** * List of the permitted subclasses. diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java b/src/main/java/org/codehaus/groovy/ast/ClassNode.java index 81fa7dd..0874d61 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java @@ -19,6 +19,7 @@ package org.codehaus.groovy.ast; import org.apache.groovy.ast.tools.ClassNodeUtils; +import org.apache.groovy.lang.annotation.Incubating; import org.codehaus.groovy.GroovyBugError; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.Expression; @@ -388,8 +389,9 @@ public class ClassNode extends AnnotatedNode { } /** - * @return permitted subclasses of sealed type + * @return permitted subclasses of sealed type, may initially be empty in early compiler phases */ + @Incubating public List<ClassNode> getPermittedSubclasses() { if (redirect != null) return redirect.getPermittedSubclasses(); @@ -397,6 +399,7 @@ public class ClassNode extends AnnotatedNode { return permittedSubclasses; } + @Incubating public void setPermittedSubclasses(List<ClassNode> permittedSubclasses) { if (redirect != null) { redirect.setPermittedSubclasses(permittedSubclasses); @@ -1420,7 +1423,10 @@ public class ClassNode extends AnnotatedNode { /** * @return true for native and emulated (annotation based) sealed classes */ + @Incubating public boolean isSealed() { + if (redirect != null) return redirect.isSealed(); + lazyClassInit(); return !getAnnotations(SEALED_TYPE).isEmpty() || !getPermittedSubclasses().isEmpty(); } diff --git a/src/main/java/org/codehaus/groovy/classgen/Verifier.java b/src/main/java/org/codehaus/groovy/classgen/Verifier.java index 1a99ca4..81314b2 100644 --- a/src/main/java/org/codehaus/groovy/classgen/Verifier.java +++ b/src/main/java/org/codehaus/groovy/classgen/Verifier.java @@ -56,7 +56,6 @@ import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; -import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; @@ -103,7 +102,6 @@ import static org.apache.groovy.ast.tools.MethodNodeUtils.getCodeAsBlock; import static org.apache.groovy.ast.tools.MethodNodeUtils.getPropertyName; import static org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptorWithoutReturnType; import static org.codehaus.groovy.ast.AnnotationNode.METHOD_TARGET; -import static org.codehaus.groovy.ast.ClassHelper.SEALED_TYPE; import static org.codehaus.groovy.ast.ClassHelper.isObjectType; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble; @@ -113,7 +111,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.block; import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX; import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX; import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; -import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; import static org.codehaus.groovy.ast.tools.GeneralUtils.declS; import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX; @@ -165,9 +162,14 @@ public class Verifier implements GroovyClassVisitor, Opcodes { // NOTE: timeStamp constants shouldn't belong to Verifier but kept here for binary compatibility public static final String __TIMESTAMP = "__timeStamp"; public static final String __TIMESTAMP__ = "__timeStamp__239_neverHappen"; + @Deprecated public static final Class<Sealed> SEALED_CLASS = Sealed.class; + @Deprecated public static final Class<NonSealed> NON_SEALED_CLASS = NonSealed.class; + private static final Class<Sealed> SEALED_TYPE = Sealed.class; + private static final Class<NonSealed> NON_SEALED_TYPE = NonSealed.class; + private ClassNode classNode; private MethodNode methodNode; @@ -243,7 +245,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes { if (classNode.getNodeMetaData(ClassNodeSkip.class) == null) { classNode.setNodeMetaData(ClassNodeSkip.class, true); } - addDetectedSealedClasses(node); return; } @@ -275,8 +276,6 @@ public class Verifier implements GroovyClassVisitor, Opcodes { checkForDuplicateMethods(node); addCovariantMethods(node); detectNonSealedClasses(node); - addDetectedSealedClasses(node); - checkFinalVariables(node); } @@ -292,8 +291,8 @@ public class Verifier implements GroovyClassVisitor, Opcodes { private void detectNonSealedClasses(ClassNode node) { if (isFinal(node.getModifiers())) return; - if (Boolean.TRUE.equals(node.getNodeMetaData(SEALED_CLASS))) return; - if (Boolean.TRUE.equals(node.getNodeMetaData(NON_SEALED_CLASS))) return; + if (Boolean.TRUE.equals(node.getNodeMetaData(SEALED_TYPE))) return; + if (Boolean.TRUE.equals(node.getNodeMetaData(NON_SEALED_TYPE))) return; ClassNode sn = node.getSuperClass(); boolean found = false; while (sn != null && !sn.equals(ClassHelper.OBJECT_TYPE)) { @@ -304,31 +303,10 @@ public class Verifier implements GroovyClassVisitor, Opcodes { sn = sn.getSuperClass(); } if (found) { - node.putNodeMetaData(NON_SEALED_CLASS, Boolean.TRUE); + node.putNodeMetaData(NON_SEALED_TYPE, Boolean.TRUE); } } - private void addDetectedSealedClasses(ClassNode node) { - boolean sealed = Boolean.TRUE.equals(node.getNodeMetaData(SEALED_CLASS)); - List<ClassNode> permitted = node.getPermittedSubclasses(); - if (!sealed || !permitted.isEmpty() || node.getModule() == null) return; - for (ClassNode possibleSubclass : node.getModule().getClasses()) { - if (possibleSubclass.getSuperClass().equals(node)) { - permitted.add(possibleSubclass); - } - for (ClassNode iface : possibleSubclass.getInterfaces()) { - if (iface.equals(node)) { - permitted.add(possibleSubclass); - } - } - } - List<Expression> names = new ArrayList<>(); - for (ClassNode next : permitted) { - names.add(classX(ClassHelper.make(next.getName()))); - } - AnnotationNode an = node.getAnnotations(SEALED_TYPE).get(0); - an.addMember("permittedSubclasses", new ListExpression(names)); - } private void checkFinalVariables(final ClassNode node) { GroovyClassVisitor visitor = new FinalVariableAnalyzer(null, getFinalVariablesCallback()); diff --git a/src/main/java/org/codehaus/groovy/transform/SealedCompletionASTTransformation.java b/src/main/java/org/codehaus/groovy/transform/SealedCompletionASTTransformation.java new file mode 100644 index 0000000..c7f880c --- /dev/null +++ b/src/main/java/org/codehaus/groovy/transform/SealedCompletionASTTransformation.java @@ -0,0 +1,81 @@ +/* + * 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 org.codehaus.groovy.transform; + +import groovy.transform.NonSealed; +import groovy.transform.Sealed; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.AnnotatedNode; +import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.control.CompilePhase; +import org.codehaus.groovy.control.SourceUnit; + +import java.util.ArrayList; +import java.util.List; + +import static org.codehaus.groovy.ast.ClassHelper.make; +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; + +/** + * Handles sealed class permitted subclass detection. + */ +@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) +public class SealedCompletionASTTransformation extends AbstractASTTransformation { + + private static final Class<Sealed> SEALED_CLASS = Sealed.class; + private static final ClassNode SEALED_TYPE = make(SEALED_CLASS); + + @Override + public void visit(ASTNode[] nodes, SourceUnit source) { + init(nodes, source); + AnnotatedNode parent = (AnnotatedNode) nodes[1]; + AnnotationNode anno = (AnnotationNode) nodes[0]; + if (!SEALED_TYPE.equals(anno.getClassNode())) return; + + if (parent instanceof ClassNode) { + addDetectedSealedClasses((ClassNode) parent); + } + } + + private void addDetectedSealedClasses(ClassNode node) { + boolean sealed = Boolean.TRUE.equals(node.getNodeMetaData(SEALED_CLASS)); + List<ClassNode> permitted = node.getPermittedSubclasses(); + if (!sealed || !permitted.isEmpty() || node.getModule() == null) return; + for (ClassNode possibleSubclass : node.getModule().getClasses()) { + if (possibleSubclass.getSuperClass().equals(node)) { + permitted.add(possibleSubclass); + } + for (ClassNode iface : possibleSubclass.getInterfaces()) { + if (iface.equals(node)) { + permitted.add(possibleSubclass); + } + } + } + List<Expression> names = new ArrayList<>(); + for (ClassNode next : permitted) { + names.add(classX(ClassHelper.make(next.getName()))); + } + AnnotationNode an = node.getAnnotations(SEALED_TYPE).get(0); + an.addMember("permittedSubclasses", new ListExpression(names)); + } +}
