Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/antlr/GroovyLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -483,7 +493,7 @@ THROW : 'throw';
THROWS : 'throws';
TRANSIENT : 'transient';
TRY : 'try';
VAL : 'val';
VAL : 'val' {isValEnabled()}?;
VAR : 'var';
VOID : 'void';
VOLATILE : 'volatile';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ public ClassNode visitTypeDeclaration(final TypeDeclarationContext ctx) {
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());
}

Expand Down Expand Up @@ -4952,6 +4952,7 @@ public List<DeclarationExpression> getDeclarationExpressions() {
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 = ".";
Expand Down
135 changes: 135 additions & 0 deletions src/test/groovy/groovy/ValDisabledTest.groovy
Original file line number Diff line number Diff line change
@@ -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"
'''
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
Loading