You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/08/07 04:52:52 UTC

[groovy] 01/01: GROOVY-10193: Support sealed type grammar

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

sunlan pushed a commit to branch GROOVY-10193
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 7471a9c1398ce956fed90b90cc5a10d933cefd93
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Aug 7 12:50:31 2021 +0800

    GROOVY-10193: Support sealed type grammar
---
 src/antlr/GroovyLexer.g4                           |  5 +++
 src/antlr/GroovyParser.g4                          |  3 ++
 .../apache/groovy/parser/antlr4/AstBuilder.java    | 34 ++++++++++++++++++++
 .../java/org/codehaus/groovy/ast/ModifierNode.java |  4 +++
 .../core/SealedTypeDeclaration_01x.groovy          | 36 ++++++++++++++++++++++
 .../fail/SealedTypeDeclaration_01x.groovy          | 21 +++++++++++++
 .../fail/SealedTypeDeclaration_02x.groovy          | 20 ++++++++++++
 .../fail/SealedTypeDeclaration_03x.groovy          | 21 +++++++++++++
 .../fail/SealedTypeDeclaration_04x.groovy          | 21 +++++++++++++
 .../fail/SealedTypeDeclaration_05x.groovy          | 21 +++++++++++++
 .../fail/SealedTypeDeclaration_06x.groovy          | 21 +++++++++++++
 .../groovy/parser/antlr4/GroovyParserTest.groovy   |  4 +++
 .../groovy/parser/antlr4/SyntaxErrorTest.groovy    |  9 ++++++
 13 files changed, 220 insertions(+)

diff --git a/src/antlr/GroovyLexer.g4 b/src/antlr/GroovyLexer.g4
index 2c58768..6c49387 100644
--- a/src/antlr/GroovyLexer.g4
+++ b/src/antlr/GroovyLexer.g4
@@ -464,12 +464,17 @@ LONG          : 'long';
 
 NATIVE        : 'native';
 NEW           : 'new';
+NON_SEALED    : 'non-sealed';
+
 PACKAGE       : 'package';
+PERMITS       : 'permits';
 PRIVATE       : 'private';
 PROTECTED     : 'protected';
 PUBLIC        : 'public';
 RETURN        : 'return';
 
+SEALED        : 'sealed';
+
 fragment
 SHORT         : 'short';
 
diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index 3faf7ad..fe4f577 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -162,6 +162,8 @@ classOrInterfaceModifier
           |   PRIVATE    // class or interface
           |   STATIC     // class or interface
           |   ABSTRACT   // class or interface
+          |   SEALED     // class or interface
+          |   NON_SEALED // class or interface
           |   FINAL      // class only -- does not apply to interfaces
           |   STRICTFP   // class or interface
           |   DEFAULT    // interface only -- does not apply to classes
@@ -227,6 +229,7 @@ locals[ int t ]
         (nls typeParameters)?
         (nls EXTENDS nls scs=typeList)?
         (nls IMPLEMENTS nls is=typeList)?
+        (nls PERMITS nls ps=typeList)?
         nls classBody[$t]
     ;
 
diff --git a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
index fa6bd54..64dbe16 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -21,6 +21,8 @@ package org.apache.groovy.parser.antlr4;
 import groovy.lang.Tuple2;
 import groovy.lang.Tuple3;
 import groovy.transform.CompileStatic;
+import groovy.transform.NonSealed;
+import groovy.transform.Sealed;
 import groovy.transform.Trait;
 import org.antlr.v4.runtime.ANTLRErrorListener;
 import org.antlr.v4.runtime.CharStream;
@@ -335,6 +337,7 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.INC;
 import static org.apache.groovy.parser.antlr4.GroovyParser.INSTANCEOF;
 import static org.apache.groovy.parser.antlr4.GroovyParser.LE;
 import static org.apache.groovy.parser.antlr4.GroovyParser.LT;
+import static org.apache.groovy.parser.antlr4.GroovyParser.NON_SEALED;
 import static org.apache.groovy.parser.antlr4.GroovyParser.NOT_IN;
 import static org.apache.groovy.parser.antlr4.GroovyParser.NOT_INSTANCEOF;
 import static org.apache.groovy.parser.antlr4.GroovyParser.PRIVATE;
@@ -343,6 +346,7 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_EXCLUSIVE_LEFT;
 import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_EXCLUSIVE_RIGHT;
 import static org.apache.groovy.parser.antlr4.GroovyParser.RANGE_INCLUSIVE;
 import static org.apache.groovy.parser.antlr4.GroovyParser.SAFE_INDEX;
+import static org.apache.groovy.parser.antlr4.GroovyParser.SEALED;
 import static org.apache.groovy.parser.antlr4.GroovyParser.STATIC;
 import static org.apache.groovy.parser.antlr4.GroovyParser.SUB;
 import static org.apache.groovy.parser.antlr4.GroovyParser.VAR;
@@ -352,6 +356,7 @@ 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.closureX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.listX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
@@ -1439,6 +1444,24 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         List<ModifierNode> modifierNodeList = ctx.getNodeMetaData(TYPE_DECLARATION_MODIFIERS);
         Objects.requireNonNull(modifierNodeList, "modifierNodeList should not be null");
         ModifierManager modifierManager = new ModifierManager(this, modifierNodeList);
+
+        Optional<ModifierNode> sealedModifierNodeOptional = modifierManager.get(SEALED);
+        Optional<ModifierNode> nonSealedModifierNodeOptional = modifierManager.get(NON_SEALED);
+        boolean isSealed = sealedModifierNodeOptional.isPresent();
+        boolean isNonSealed = nonSealedModifierNodeOptional.isPresent();
+        if ((isAnnotation || isEnum) && (isSealed || isNonSealed)) {
+            ModifierNode mn = isSealed ? sealedModifierNodeOptional.get() : nonSealedModifierNodeOptional.get();
+            throw createParsingFailedException("modifier `" + mn.getText() + "` is not allowed here", mn);
+        }
+
+        boolean hasPermits = asBoolean(ctx.PERMITS());
+        if (isSealed && !hasPermits) {
+            throw createParsingFailedException("sealed type declaration should have `permits` clause", ctx);
+        }
+        if (isNonSealed && hasPermits) {
+            throw createParsingFailedException("non-sealed type declaration should not have `permits` clause", ctx);
+        }
+
         int modifiers = modifierManager.getClassModifiersOpValue();
 
         boolean syntheticPublic = ((modifiers & Opcodes.ACC_SYNTHETIC) != 0);
@@ -1474,6 +1497,17 @@ public class AstBuilder extends GroovyParserBaseVisitor<Object> {
         classNode.setGenericsTypes(this.visitTypeParameters(ctx.typeParameters()));
         boolean isInterfaceWithDefaultMethods = (isInterface && this.containsDefaultMethods(ctx));
 
+        if (isSealed) {
+            AnnotationNode sealedAnnotationNode = new AnnotationNode(ClassHelper.makeCached(Sealed.class));
+            ListExpression permittedSubclassesListExpression =
+                    listX(Arrays.stream(this.visitTypeList(ctx.ps))
+                            .map(ClassExpression::new)
+                            .collect(Collectors.toList()));
+            sealedAnnotationNode.setMember("permittedSubclasses", permittedSubclassesListExpression);
+            classNode.addAnnotation(sealedAnnotationNode);
+        } else if (isNonSealed) {
+            classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(NonSealed.class)));
+        }
         if (isInterfaceWithDefaultMethods || asBoolean(ctx.TRAIT())) {
             classNode.addAnnotation(new AnnotationNode(ClassHelper.makeCached(Trait.class)));
         }
diff --git a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
index e8a2f9b..e76e1b4 100644
--- a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java
@@ -29,9 +29,11 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.DEF;
 import static org.apache.groovy.parser.antlr4.GroovyParser.DEFAULT;
 import static org.apache.groovy.parser.antlr4.GroovyParser.FINAL;
 import static org.apache.groovy.parser.antlr4.GroovyParser.NATIVE;
+import static org.apache.groovy.parser.antlr4.GroovyParser.NON_SEALED;
 import static org.apache.groovy.parser.antlr4.GroovyParser.PRIVATE;
 import static org.apache.groovy.parser.antlr4.GroovyParser.PROTECTED;
 import static org.apache.groovy.parser.antlr4.GroovyParser.PUBLIC;
+import static org.apache.groovy.parser.antlr4.GroovyParser.SEALED;
 import static org.apache.groovy.parser.antlr4.GroovyParser.STATIC;
 import static org.apache.groovy.parser.antlr4.GroovyParser.STRICTFP;
 import static org.apache.groovy.parser.antlr4.GroovyParser.SYNCHRONIZED;
@@ -66,6 +68,8 @@ public class ModifierNode extends ASTNode {
             PRIVATE, Opcodes.ACC_PRIVATE,
             STATIC, Opcodes.ACC_STATIC,
             ABSTRACT, Opcodes.ACC_ABSTRACT,
+            SEALED, 0,
+            NON_SEALED, 0,
             FINAL, Opcodes.ACC_FINAL,
             STRICTFP, Opcodes.ACC_STRICT,
             DEFAULT, 0 // no flag for specifying a default method in the JVM spec, hence no ACC_DEFAULT flag in ASM
diff --git a/src/test-resources/core/SealedTypeDeclaration_01x.groovy b/src/test-resources/core/SealedTypeDeclaration_01x.groovy
new file mode 100644
index 0000000..eec62f4
--- /dev/null
+++ b/src/test-resources/core/SealedTypeDeclaration_01x.groovy
@@ -0,0 +1,36 @@
+import groovy.transform.NonSealed
+import groovy.transform.Sealed
+
+/*
+ *  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.
+ */
+
+sealed interface ShapeI permits Circle, Rectangle { }
+final class Circle implements ShapeI { }
+non-sealed class Rectangle implements ShapeI { }
+final class Square extends Rectangle { }
+
+def c = new Circle()
+def r = new Rectangle()
+def s = new Square()
+assert [c, r, s]*.class.name == ['Circle', 'Rectangle', 'Square']
+
+def shapeIAnnotations = ShapeI.class.annotations
+assert 1 == shapeIAnnotations.size()
+Sealed sealedAnnotation = (Sealed) shapeIAnnotations[0]
+assert [Circle.class, Rectangle.class] == sealedAnnotation.permittedSubclasses()
diff --git a/src/test-resources/fail/SealedTypeDeclaration_01x.groovy b/src/test-resources/fail/SealedTypeDeclaration_01x.groovy
new file mode 100644
index 0000000..bd3157f
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_01x.groovy
@@ -0,0 +1,21 @@
+/*
+ *  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.
+ */
+
+sealed @interface ShapeI permits Circle, Rectangle { }
+
diff --git a/src/test-resources/fail/SealedTypeDeclaration_02x.groovy b/src/test-resources/fail/SealedTypeDeclaration_02x.groovy
new file mode 100644
index 0000000..3fab635
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_02x.groovy
@@ -0,0 +1,20 @@
+/*
+ *  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.
+ */
+
+sealed enum ShapeI permits Circle, Rectangle { }
diff --git a/src/test-resources/fail/SealedTypeDeclaration_03x.groovy b/src/test-resources/fail/SealedTypeDeclaration_03x.groovy
new file mode 100644
index 0000000..046b9e7
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_03x.groovy
@@ -0,0 +1,21 @@
+/*
+ *  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.
+ */
+
+non-sealed @interface ShapeI { }
+
diff --git a/src/test-resources/fail/SealedTypeDeclaration_04x.groovy b/src/test-resources/fail/SealedTypeDeclaration_04x.groovy
new file mode 100644
index 0000000..e94fcb4
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_04x.groovy
@@ -0,0 +1,21 @@
+/*
+ *  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.
+ */
+
+non-sealed enum ShapeI { }
+
diff --git a/src/test-resources/fail/SealedTypeDeclaration_05x.groovy b/src/test-resources/fail/SealedTypeDeclaration_05x.groovy
new file mode 100644
index 0000000..83ca693
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_05x.groovy
@@ -0,0 +1,21 @@
+/*
+ *  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.
+ */
+
+sealed interface ShapeI { }
+
diff --git a/src/test-resources/fail/SealedTypeDeclaration_06x.groovy b/src/test-resources/fail/SealedTypeDeclaration_06x.groovy
new file mode 100644
index 0000000..40523da
--- /dev/null
+++ b/src/test-resources/fail/SealedTypeDeclaration_06x.groovy
@@ -0,0 +1,21 @@
+/*
+ *  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.
+ */
+
+non-sealed interface ShapeI permits Circle, Rectangle { }
+
diff --git a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
index b6d87a7..b2b3e90 100644
--- a/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy
@@ -361,6 +361,10 @@ final class GroovyParserTest extends GroovyTestCase {
         doTest('core/AnnotationDeclaration_01.groovy')
     }
 
+    void "test groovy core - SealedTypeDeclaration"() {
+        doRunAndTestAntlr4('core/SealedTypeDeclaration_01x.groovy')
+    }
+
     void "test groovy core - Command"() {
         doTest('core/Command_01.groovy')
         doTest('core/Command_02.groovy')
diff --git a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
index e089e02..dae25d9 100644
--- a/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
+++ b/src/test/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy
@@ -306,6 +306,15 @@ final class SyntaxErrorTest extends GroovyTestCase {
             |'''.stripMargin()
     }
 
+    void 'test groovy core - SealedTypeDeclaration'() {
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_01x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_02x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_03x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_04x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_05x.groovy')
+        TestUtils.doRunAndShouldFail('fail/SealedTypeDeclaration_06x.groovy')
+    }
+
     void 'test groovy core - MethodDeclaration'() {
         TestUtils.shouldFail('fail/MethodDeclaration_01.groovy')
         TestUtils.doRunAndShouldFail('fail/MethodDeclaration_02x.groovy')