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

paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new 19826e18dc GROOVY-11994: GEP-16: Missing groovy.val.enabled flag
19826e18dc is described below

commit 19826e18dcb7bad60beadaae219a5ca7b90f827c
Author: Paul King <[email protected]>
AuthorDate: Tue May 5 06:47:30 2026 +1000

    GROOVY-11994: GEP-16: Missing groovy.val.enabled flag
---
 src/antlr/GroovyLexer.g4                           |  12 +-
 .../apache/groovy/parser/antlr4/AstBuilder.java    |   3 +-
 src/test/groovy/groovy/ValDisabledTest.groovy      | 135 +++++++++++++++++++++
 .../m12n/ExtensionModuleHelperForTests.groovy      |  20 ++-
 4 files changed, 166 insertions(+), 4 deletions(-)

diff --git a/src/antlr/GroovyLexer.g4 b/src/antlr/GroovyLexer.g4
index 7f4ff706fa..c5215b4c1c 100644
--- a/src/antlr/GroovyLexer.g4
+++ b/src/antlr/GroovyLexer.g4
@@ -50,6 +50,16 @@ options {
     private int  lastTokenType;
     private int  invalidDigitCount;
 
+    /**
+     * When {@code false}, the {@code val} keyword is treated as a regular
+     * identifier (lexed as IDENTIFIER, not VAL). This can be used as a porting
+     * aid for migrating to Groovy 6 if affected by the known breaking edge 
cases.
+     * Controlled by system property {@code groovy.val.enabled} (default: 
{@code true}).
+     */
+    private static final boolean VAL_ENABLED =
+            Boolean.parseBoolean(System.getProperty("groovy.val.enabled", 
"true"));
+    private boolean isValEnabled() { return VAL_ENABLED; }
+
     /**
      * Record the index and token type of the current token while emitting 
tokens.
      */
@@ -483,7 +493,7 @@ THROW         : 'throw';
 THROWS        : 'throws';
 TRANSIENT     : 'transient';
 TRY           : 'try';
-VAL           : 'val';
+VAL           : 'val' {isValEnabled()}?;
 VAR           : 'var';
 VOID          : 'void';
 VOLATILE      : 'volatile';
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 770a0cad50..bc8f2cf2b9 100644
--- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
+++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java
@@ -1225,7 +1225,7 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
     public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
         String packageName = 
Optional.ofNullable(this.moduleNode.getPackageName()).orElse("");
         String className = this.visitIdentifier(ctx.identifier());
-        if ("var".equals(className) || "val".equals(className)) {
+        if ("var".equals(className) || (VAL_ENABLED && 
"val".equals(className))) {
             throw createParsingFailedException(className + " cannot be used 
for type declarations", ctx.identifier());
         }
 
@@ -4952,6 +4952,7 @@ public class AstBuilder extends 
GroovyParserBaseVisitor<Object> {
     private int visitingArrayInitializerCount;
 
     private static final int SLL_THRESHOLD = 
SystemUtil.getIntegerSafe("groovy.antlr4.sll.threshold", -1);
+    private static final boolean VAL_ENABLED = 
Boolean.parseBoolean(System.getProperty("groovy.val.enabled", "true"));
 
     private static final String QUESTION_STR = "?";
     private static final String DOT_STR = ".";
diff --git a/src/test/groovy/groovy/ValDisabledTest.groovy 
b/src/test/groovy/groovy/ValDisabledTest.groovy
new file mode 100644
index 0000000000..3eebe4aed7
--- /dev/null
+++ b/src/test/groovy/groovy/ValDisabledTest.groovy
@@ -0,0 +1,135 @@
+/*
+ *  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 groovy
+
+import org.junit.jupiter.api.Test
+
+import static 
org.codehaus.groovy.runtime.m12n.ExtensionModuleHelperForTests.doInFork
+
+/**
+ * Tests that when {@code groovy.val.enabled=false}, GEP-16 breaking
+ * changes are resolved and {@code val} behaves as a regular identifier.
+ *
+ * Each test runs in a freshly forked JVM (compile + execution) with the
+ * property set, so the lexer's {@code static final VAL_ENABLED} is
+ * initialised to {@code false}.
+ */
+final class ValDisabledTest {
+
+    private static final List<String> JVM_ARGS = ['-Dgroovy.val.enabled=false']
+
+    private static void doInForkWithValDisabled(String script) {
+        // Wrap each snippet in assertScript so top-level class declarations 
work
+        // (the snippet is otherwise placed inside a method body where local
+        // classes are not supported).
+        doInFork('java.lang.Object', "assertScript '''${script.replace("'", 
"\\'")}'''", JVM_ARGS)
+    }
+
+    @Test
+    void testFieldNamedValBeforeMethod() {
+        doInForkWithValDisabled '''
+            class Foo {
+                def val
+                void doSomething() {}
+            }
+            def f = new Foo()
+            f.val = 42
+            assert f.val == 42
+        '''
+    }
+
+    @Test
+    void testValAsCastExpression() {
+        doInForkWithValDisabled '''
+            def val = 42
+            def result = val as String
+            assert result == "42"
+        '''
+    }
+
+    @Test
+    void testClassNamedVal() {
+        doInForkWithValDisabled '''
+            class val {
+                int x
+            }
+            def v = new val(x: 5)
+            assert v.x == 5
+        '''
+    }
+
+    @Test
+    void testValAsMethodReturnType() {
+        doInForkWithValDisabled '''
+            class val {
+                int x
+            }
+            import val as Val
+            class Foo {
+                Val bar() { new val(x: 99) }
+            }
+            assert new Foo().bar().x == 99
+        '''
+    }
+
+    @Test
+    void testValAsExplicitType() {
+        doInForkWithValDisabled '''
+            class val {
+                int x
+            }
+            val v = new val(x: 7)
+            assert v.x == 7
+        '''
+    }
+
+    @Test
+    void testDefValAssignment() {
+        doInForkWithValDisabled '''
+            def val = 1
+            assert val == 1
+        '''
+    }
+
+    @Test
+    void testValReassignment() {
+        doInForkWithValDisabled '''
+            def val = 1
+            val = 2
+            assert val == 2
+        '''
+    }
+
+    @Test
+    void testValAsMapKey() {
+        doInForkWithValDisabled '''
+            def m = [val: 42]
+            assert m.val == 42
+        '''
+    }
+
+    @Test
+    void testValPropertyAccess() {
+        doInForkWithValDisabled '''
+            class Foo { def val = "hello" }
+            def f = new Foo()
+            assert f.val == "hello"
+        '''
+    }
+}
diff --git 
a/src/test/groovy/org/codehaus/groovy/runtime/m12n/ExtensionModuleHelperForTests.groovy
 
b/src/test/groovy/org/codehaus/groovy/runtime/m12n/ExtensionModuleHelperForTests.groovy
index 2281e11aa1..bd36f05855 100644
--- 
a/src/test/groovy/org/codehaus/groovy/runtime/m12n/ExtensionModuleHelperForTests.groovy
+++ 
b/src/test/groovy/org/codehaus/groovy/runtime/m12n/ExtensionModuleHelperForTests.groovy
@@ -25,6 +25,10 @@ final class ExtensionModuleHelperForTests {
     private ExtensionModuleHelperForTests() {}
 
     static void doInFork(String baseTestClass = 'java.lang.Object', String 
code) {
+        doInFork(baseTestClass, code, Collections.<String>emptyList())
+    }
+
+    static void doInFork(String baseTestClass, String code, List<String> 
extraJvmArgs) {
         File baseDir = File.createTempDir()
         File sourceFile = new File(baseDir, 'Temp.groovy')
         sourceFile << """import org.codehaus.groovy.runtime.m12n.*
@@ -69,6 +73,7 @@ final class ExtensionModuleHelperForTests {
             ~/Picked up _JAVA_OPTIONS: .*/
         ]
         def jvmArgs = []
+        jvmArgs.addAll(extraJvmArgs)
         if (Runtime.version().feature() == 25) {
             // JEP 471/498: silence terminal-deprecation warnings for 
sun.misc.Unsafe
             // memory-access methods called from agents on the inherited 
classpath
@@ -78,8 +83,19 @@ final class ExtensionModuleHelperForTests {
         }
         try {
             ant.with {
-                taskdef(name: 'groovyc', classname: 
'org.codehaus.groovy.ant.Groovyc')
-                groovyc(srcdir: baseDir.absolutePath, destdir: 
baseDir.absolutePath, includes: 'Temp.groovy', fork: true)
+                // Compile via FileSystemCompilerFacade in a forked JVM (same 
as forked groovyc),
+                // but using ant.java so we can attach arbitrary JVM args 
(e.g. system properties).
+                java(classname: 
'org.codehaus.groovy.ant.FileSystemCompilerFacade', fork: 'true', failonerror: 
'true') {
+                    jvmArgs.each { jvmarg(value: it) }
+                    classpath {
+                        cp.each { pathelement location: it }
+                    }
+                    arg(value: '--classpath')
+                    arg(value: cp.join(File.pathSeparator))
+                    arg(value: '-d')
+                    arg(value: baseDir.absolutePath)
+                    arg(value: sourceFile.absolutePath)
+                }
                 java(classname: 'Temp', fork: 'true', outputproperty: 'out', 
errorproperty: 'err') {
                     jvmArgs.each { jvmarg(value: it) }
                     classpath {

Reply via email to