You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/01/06 11:35:29 UTC

[groovy] 01/05: GROOVY-10433: "sealed" not usable as a restricted identifier

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 8f747afcfd60d5d84a101248c3ae1456834d532c
Author: Paul King <pa...@asert.com.au>
AuthorDate: Fri Dec 31 20:09:19 2021 +1000

    GROOVY-10433: "sealed" not usable as a restricted identifier
---
 src/antlr/GroovyParser.g4               |   1 +
 src/spec/doc/core-differences-java.adoc |   7 +-
 src/spec/doc/core-syntax.adoc           | 112 ++++++++++++---------
 src/spec/test/SyntaxTest.groovy         | 173 ++++++++++++++++++++++++++++++++
 4 files changed, 243 insertions(+), 50 deletions(-)

diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4
index 6f2b8f9..9d26199 100644
--- a/src/antlr/GroovyParser.g4
+++ b/src/antlr/GroovyParser.g4
@@ -1220,6 +1220,7 @@ identifier
     |   AS
     |   YIELD
     |   PERMITS
+    |   SEALED
     |   RECORD
     ;
 
diff --git a/src/spec/doc/core-differences-java.adoc b/src/spec/doc/core-differences-java.adoc
index 5c4a937..cbb5264 100644
--- a/src/spec/doc/core-differences-java.adoc
+++ b/src/spec/doc/core-differences-java.adoc
@@ -22,6 +22,9 @@
 ifndef::core-semantics[]
 :core-semantics: core-semantics.adoc
 endif::[]
+ifndef::core-syntax[]
+:core-syntax: core-syntax.adoc
+endif::[]
 
 = Differences with Java
 
@@ -500,7 +503,7 @@ Other conversions have their behavior defined by `java.lang.Number`.
 
 == Extra keywords
 
-Groovy has many of the same keywords as Java and Groovy 3 also has the same `var` reserved type as Java.
+Groovy has many of the same keywords as Java and Groovy 3 and above also has the same `var` reserved type as Java.
 In addition, Groovy has the following keywords:
 
 * `as`
@@ -514,3 +517,5 @@ e.g. the following is valid: `var var = [def: 1, as: 2, in: 3, trait: 4]`.
 Never-the-less, you are discouraged from using the above keywords in places that might cause confusion even when
 the compiler might be happy. In particular, avoid using them for variable, method and class names,
 so our previous `var var` example would be considered poor style.
+
+Additional documentation is available for <<{core-syntax}#_keywords,keywords>>.
diff --git a/src/spec/doc/core-syntax.adoc b/src/spec/doc/core-syntax.adoc
index 691f3df..a60cff8 100644
--- a/src/spec/doc/core-syntax.adoc
+++ b/src/spec/doc/core-syntax.adoc
@@ -116,60 +116,74 @@ NOTE: The `#` character must be the first character of the file. Any indentation
 
 == Keywords
 
-The following list represents all the keywords of the Groovy language:
+Groovy has the following reserved keywords:
 
 [cols="1,1,1,1"]
-.Keywords
+.Reserved Keywords
 |===
-|as
-|assert
-|break
-|case
-
-|catch
-|class
-|const
-|continue
-
-|def
-|default
-|do
-|else
-
-|enum
-|extends
-|false
-|finally
-
-|for
-|goto
-|if
-|implements
-
-|import
-|in
-|instanceof
-|interface
-
-|new
-|null
-|package
-|return
-
-|super
-|switch
-|this
-|throw
-
-|throws
-|trait
-|true
-|try
-
-|var
-|while | |
+include::../test/SyntaxTest.groovy[tags=reserved_keywords,indent=0]
+|===
+
+Of these, `const`, `goto`, `strictfp`, and `threadsafe` are not currently in use.
+
+The reserved keywords can't in general be used for variable, field and method names.
+
+****
+A trick allows methods to be defined having the same name as a keyword
+by surrounding the name in quotes as shown in the following example:
+
+[source,groovy]
+----
+include::../test/SyntaxTest.groovy[tags=reserved_keywords_example,indent=0]
+----
+
+Using such names might be confusing and is often best to avoid.
+The trick is primarily intended to enable certain Java integration scenarios
+and certain link:core-domain-specific-languages.html[DSL] scenarios where
+having "verbs" and "nouns" with the same name as keywords may be desirable.
+****
 
+In addition, Groovy has the following contextual keywords:
+
+[cols="1,1,1,1"]
+.Contextual Keywords
 |===
+include::../test/SyntaxTest.groovy[tags=contextual_keywords,indent=0]
+|===
+
+These words are only keywords in certain contexts and can be more freely used in some places,
+in particular for variables, fields and method names.
+
+****
+This extra lenience allows using method or variable names that were not keywords in earlier
+versions of Groovy or are not keywords in Java. Examples are shown here:
+
+[source,groovy]
+----
+include::../test/SyntaxTest.groovy[tags=contextual_keywords_examples,indent=0]
+----
+Groovy programmers familiar with these contextual keywords may still wish to avoid
+using those names unless there is a good reason to use such a name.
+****
+
+The restrictions on reserved keywords also apply for the
+primitive types, the boolean literals and the null literal (all of which are discussed later):
+
+[cols="1,1,1,1"]
+.Other reserved words
+|===
+include::../test/SyntaxTest.groovy[tags=reserved_words,indent=0]
+|===
+
+****
+While not recommended, the same trick as for reserved keywords can be used:
+[source,groovy]
+----
+include::../test/SyntaxTest.groovy[tags=reserved_words_example,indent=0]
+----
+Using such words as method names is potentially confusing and is often best to avoid, however,
+it might be useful for certain kinds of link:core-domain-specific-languages.html[DSLs].
+****
 
 == Identifiers
 
diff --git a/src/spec/test/SyntaxTest.groovy b/src/spec/test/SyntaxTest.groovy
index 7fbb437..9813893 100644
--- a/src/spec/test/SyntaxTest.groovy
+++ b/src/spec/test/SyntaxTest.groovy
@@ -17,6 +17,7 @@
  *  under the License.
  */
 import gls.CompilableTestSupport
+import org.codehaus.groovy.control.CompilationFailedException
 
 class SyntaxTest extends CompilableTestSupport {
 
@@ -793,7 +794,179 @@ class SyntaxTest extends CompilableTestSupport {
         assert person.containsKey('name')    // <2>
         assert !person.containsKey('key')    // <3>
         // end::variable_key_2[]
+    }
 
+    void testReservedKeywords() {
+        def rk = '''
+        // tag::reserved_keywords[]
+        |abstract
+        |assert
+        |break
+        |case
+
+        |catch
+        |class
+        |const
+        |continue
+
+        |def
+        |default
+        |do
+        |else
+
+        |enum
+        |extends
+        |final
+        |finally
+
+        |for
+        |goto
+        |if
+        |implements
+
+        |import
+        |instanceof
+        |interface
+        |native
+
+        |new
+        |null
+        |non-sealed
+        |package
+
+        |public
+        |protected
+        |private
+        |return
+
+        |static
+        |strictfp
+        |super
+        |switch
+
+        |synchronized
+        |this
+        |threadsafe
+        |throw
+
+        |throws
+        |transient
+        |try
+        |while
+        // end::reserved_keywords[]
+        '''.readLines()[2..-3].join()
+        def reservedKeywords = rk.split(/(?m)[|]/)*.trim().grep()
+        def shell = new GroovyShell()
+        reservedKeywords.each {
+            /*
+            // tag::reserved_keywords_example[]
+            // reserved keywords can be used for method names if quoted
+            def "abstract"() { true }
+            // when calling such methods, the name must be qualified using "this."
+            this.abstract()
+            // end::reserved_keywords_example[]
+            */
+            assertScript """
+            def "$it"() { true }
+            assert this.$it()
+            """
+            shouldFail(CompilationFailedException) {
+                shell.parse """
+                    def $it() { true }
+                """
+            }
+            shouldFail(CompilationFailedException) {
+                shell.parse """
+                    def $it = true
+                """
+            }
+        }
+    }
 
+    void testContextualKeywords() {
+        def ck = '''
+        // tag::contextual_keywords[]
+        | as
+        | in
+        | permits
+        | record
+        | sealed
+        | trait
+        | var
+        | yields
+        // end::contextual_keywords[]
+        '''.readLines()[2..-3].join()
+        def contextualKeywords = ck.split(/(?m)[|]/)*.trim().grep()
+        def shell = new GroovyShell()
+        /*
+        // tag::contextual_keywords_examples[]
+        // contextual keywords can be used for field and variable names
+        def as = true
+        assert as
+
+        // contextual keywords can be used for method names
+        def in() { true }
+        // when calling such methods, the name only needs to be qualified using "this." in scenarios which would be ambiguous
+        this.in()
+        // end::contextual_keywords_examples[]
+        */
+        contextualKeywords.each {
+            assertScript """
+            def $it = true
+            assert $it
+            def $it() { true }
+            this.$it()
+            """
+            shouldFail(CompilationFailedException) {
+                shell.parse """
+                    def $it() { true }
+                    $it()
+                """
+            }
+        }
+    }
+
+    void testOtherReservedWords() {
+        def rw = '''
+        // tag::reserved_words[]
+        | null
+        | true
+        | false
+        | boolean
+        | char
+        | byte
+        | short
+        | int
+        | long
+        | float
+        | double
+        |
+        // end::reserved_words[]
+        '''.readLines()[2..-3].join()
+        def reservedWords = rw.split(/(?m)[|]/)*.trim().grep()
+        def shell = new GroovyShell()
+        /*
+        // tag::reserved_words_example[]
+        def "null"() { true }  // not recommended; potentially confusing
+        assert this.null()     // must be qualified
+        // end::reserved_words_example[]
+        */
+        reservedWords.each {
+            assertScript """
+            def "$it"() { true }
+            assert this.$it()
+            """
+            shouldFail(CompilationFailedException) {
+                shell.parse """
+                    def $it() { true }
+                """
+            }
+            shouldFail(CompilationFailedException) {
+                shell.parse """
+                    def $it = true
+                """
+            }
+
+        }
     }
 }