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')