diff --git a/src/antlr/GroovyLexer.g4 b/src/antlr/GroovyLexer.g4 index 79ddf78dbd5..dcb9a8b2ff9 100644 --- a/src/antlr/GroovyLexer.g4 +++ b/src/antlr/GroovyLexer.g4 @@ -479,6 +479,7 @@ THROW : 'throw'; THROWS : 'throws'; TRANSIENT : 'transient'; TRY : 'try'; +VAL : 'val'; VAR : 'var'; VOID : 'void'; VOLATILE : 'volatile'; diff --git a/src/antlr/GroovyParser.g4 b/src/antlr/GroovyParser.g4 index 550852286a1..9da657d7523 100644 --- a/src/antlr/GroovyParser.g4 +++ b/src/antlr/GroovyParser.g4 @@ -133,6 +133,7 @@ modifier | TRANSIENT | VOLATILE | DEF + | VAL | VAR ) ; @@ -174,6 +175,7 @@ variableModifier : annotation | m=( FINAL | DEF + | VAL | VAR // Groovy supports declaring local variables as instance/class fields, // e.g. import groovy.transform.*; @Field static List awe = [1, 2, 3] @@ -698,7 +700,7 @@ enhancedForControl ; indexVariable - : (BuiltInPrimitiveType | DEF | VAR)? identifier + : (BuiltInPrimitiveType | DEF | VAL | VAR)? identifier ; originalForControl @@ -1234,6 +1236,7 @@ identifier | RECORD | SEALED | TRAIT + | VAL | VAR | YIELD ; @@ -1289,6 +1292,7 @@ keywords | TRAIT | THREADSAFE | TRY + | VAL | VAR | VOLATILE | WHILE 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 07331823f85..bd79a8966d4 100644 --- a/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java +++ b/src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java @@ -1122,8 +1122,8 @@ 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)) { - throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier()); + if ("var".equals(className) || "val".equals(className)) { + throw createParsingFailedException(className + " cannot be used for type declarations", ctx.identifier()); } boolean isAnnotation = asBoolean(ctx.AT()); @@ -1644,8 +1644,8 @@ public MethodNode visitCompactConstructorDeclaration(final CompactConstructorDec throw createParsingFailedException("Only record can have compact constructor", ctx); } - if (new ModifierManager(this, ctx.getNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS)).containsAny(VAR)) { - throw createParsingFailedException("var cannot be used for compact constructor declaration", ctx); + if (new ModifierManager(this, ctx.getNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS)).containsAny(VAL, VAR)) { + throw createParsingFailedException("val/var cannot be used for compact constructor declaration", ctx); } String methodName = this.visitMethodName(ctx.methodName()); @@ -1682,8 +1682,8 @@ public void visitPropertyExpression(final PropertyExpression expression) { public MethodNode visitMethodDeclaration(final MethodDeclarationContext ctx) { ModifierManager modifierManager = createModifierManager(ctx); - if (modifierManager.containsAny(VAR)) { - throw createParsingFailedException("var cannot be used for method declarations", ctx); + if (modifierManager.containsAny(VAL, VAR)) { + throw createParsingFailedException("val/var cannot be used for method return types", ctx); } String methodName = this.visitMethodName(ctx.methodName()); @@ -4492,7 +4492,7 @@ private boolean isSyntheticPublic(final boolean isAnnotationDeclaration, final b return true; } - if (hasReturnType && (modifierManager.containsAny(DEF, VAR))) { + if (hasReturnType && (modifierManager.containsAny(DEF, VAL, VAR))) { return true; } diff --git a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java index e76e1b48499..079364c4abc 100644 --- a/src/main/java/org/codehaus/groovy/ast/ModifierNode.java +++ b/src/main/java/org/codehaus/groovy/ast/ModifierNode.java @@ -38,6 +38,7 @@ import static org.apache.groovy.parser.antlr4.GroovyParser.STRICTFP; import static org.apache.groovy.parser.antlr4.GroovyParser.SYNCHRONIZED; import static org.apache.groovy.parser.antlr4.GroovyParser.TRANSIENT; +import static org.apache.groovy.parser.antlr4.GroovyParser.VAL; import static org.apache.groovy.parser.antlr4.GroovyParser.VAR; import static org.apache.groovy.parser.antlr4.GroovyParser.VOLATILE; import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; @@ -56,6 +57,7 @@ public class ModifierNode extends ASTNode { public static final Map MODIFIER_OPCODE_MAP = Maps.of( ANNOTATION_TYPE, 0, DEF, 0, + VAL, Opcodes.ACC_FINAL, VAR, 0, NATIVE, Opcodes.ACC_NATIVE, @@ -129,7 +131,11 @@ public boolean isAnnotation() { } public boolean isDef() { - return Objects.equals(DEF, this.type) || Objects.equals(VAR, this.type); + return Objects.equals(DEF, this.type) || Objects.equals(VAL, this.type) || Objects.equals(VAR, this.type); + } + + public boolean isVal() { + return Objects.equals(VAL, this.type); } public Integer getType() { diff --git a/src/test-resources/core/Val_01x.groovy b/src/test-resources/core/Val_01x.groovy new file mode 100644 index 00000000000..e9444001ccb --- /dev/null +++ b/src/test-resources/core/Val_01x.groovy @@ -0,0 +1,57 @@ +/* + * 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. + */ + +// basic val declaration +val name = "Groovy" +assert "Groovy" == name + +// val as closure parameter name +[1, 2, 3].each { val -> + assert 0 < val && val < 4 +} + +// val as variable name (contextual keyword) +val val = "val variable name" +assert "val variable name" == val + +// val in map key +def m = [val: 42] +assert m.val == 42 + +// val with different types +val x = 42 +assert x == 42 +val s = "hello" +assert s.class == String + +// shallow finality - mutation OK +val list = [1, 2, 3] +list << 4 +assert list == [1, 2, 3, 4] + +// final val is redundant but works +final val y = 10 +assert y == 10 + +// for loop with val +for (val i in [1, 2, 3]) { assert i > 0 } + +// GString interpolation +val g = 99 +assert "$g" == "99" diff --git a/src/test-resources/fail/Val_01x.groovy b/src/test-resources/fail/Val_01x.groovy new file mode 100644 index 00000000000..0748f66b32b --- /dev/null +++ b/src/test-resources/fail/Val_01x.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. + */ + +class val {} diff --git a/src/test-resources/fail/Val_02x.groovy b/src/test-resources/fail/Val_02x.groovy new file mode 100644 index 00000000000..ed6e519f7d6 --- /dev/null +++ b/src/test-resources/fail/Val_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. + */ + +val someMethod() {} diff --git a/src/test-resources/fail/Val_03x.groovy b/src/test-resources/fail/Val_03x.groovy new file mode 100644 index 00000000000..9751976344d --- /dev/null +++ b/src/test-resources/fail/Val_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. + */ + +val x = 1 +x = 2 diff --git a/src/test/groovy/bugs/Groovy5358.groovy b/src/test/groovy/bugs/Groovy5358.groovy index d6d6f631107..9c7fcec5e85 100644 --- a/src/test/groovy/bugs/Groovy5358.groovy +++ b/src/test/groovy/bugs/Groovy5358.groovy @@ -71,25 +71,25 @@ final class Groovy5358 { void testSetPropertyOverrides() { assertScript ''' class FooWorksAsMap { - def val + def stored void setProperty(String name, value) { - val = "OK:FooWorksAsMap.$value" + stored = "OK:FooWorksAsMap.$value" } } class BarWorksAsMap { - def val + def stored } @Category(BarWorksAsMap) class C { void setProperty(String name, value) { - setVal("OK:BarWorksAsMap.$value") + setStored("OK:BarWorksAsMap.$value") } } BarWorksAsMap.mixin C class BazWorksAsMap { - def val + def stored } BazWorksAsMap.metaClass.setProperty = { String name, value -> - setVal("OK:BazWorksAsMap.$value") + setStored("OK:BazWorksAsMap.$value") } def objects = [ @@ -99,10 +99,10 @@ final class Groovy5358 { [:] ] for (def obj in objects) { - def which = "${obj.getClass().getSimpleName()}.val" + def which = "${obj.getClass().getSimpleName()}.stored" try { - obj.val = which.startsWith('LinkedHashMap') ? "OK:LinkedHashMap.bar" : 'bar' - assert obj.val.startsWith('OK:') : "$which -> $obj.val" + obj.stored = which.startsWith('LinkedHashMap') ? "OK:LinkedHashMap.bar" : 'bar' + assert obj.stored.startsWith('OK:') : "$which -> $obj.stored" } catch (any) { assert false : "$which -> FAIL:$any" } diff --git a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy index f69eb1a332d..90a376842a6 100644 --- a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy +++ b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy @@ -815,7 +815,7 @@ final class LambdaTest { this.val = v } String toString() { - val as String + this.val as String } def Value replace(Supplier supplier) { new Value<>(supplier.get()) diff --git a/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy b/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy index 1abb9bd8cb1..20e16129de6 100644 --- a/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy +++ b/src/test/groovy/org/apache/groovy/parser/antlr4/GroovyParserTest.groovy @@ -482,6 +482,11 @@ final class GroovyParserTest { doRunAndTestAntlr4('core/SafeChainOperator.groovy') } + @Test + void 'groovy core - val'() { + doRunAndTestAntlr4('core/Val_01x.groovy') + } + @Test void 'groovy core - var'() { doRunAndTestAntlr4('core/Var_01x.groovy') diff --git a/src/test/groovy/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy b/src/test/groovy/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy index 788f47e8eff..bbe0175f72c 100644 --- a/src/test/groovy/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy +++ b/src/test/groovy/org/apache/groovy/parser/antlr4/SyntaxErrorTest.groovy @@ -480,6 +480,13 @@ final class SyntaxErrorTest { TestUtils.doRunAndShouldFail('fail/MethodCall_01x.groovy') } + @Test + void 'groovy core - val'() { + TestUtils.doRunAndShouldFail('fail/Val_01x.groovy') + TestUtils.doRunAndShouldFail('fail/Val_02x.groovy') + TestUtils.doRunAndShouldFail('fail/Val_03x.groovy') + } + @Test void 'groovy core - var'() { TestUtils.doRunAndShouldFail('fail/Var_01x.groovy') diff --git a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java index 31a22c99f19..284d718d4f5 100644 --- a/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java +++ b/subprojects/groovy-console/src/main/groovy/groovy/console/ui/text/SmartDocumentFilter.java @@ -110,6 +110,7 @@ import static org.apache.groovy.parser.antlr4.GroovyLexer.TRANSIENT; import static org.apache.groovy.parser.antlr4.GroovyLexer.TRY; import static org.apache.groovy.parser.antlr4.GroovyLexer.UNEXPECTED_CHAR; +import static org.apache.groovy.parser.antlr4.GroovyLexer.VAL; import static org.apache.groovy.parser.antlr4.GroovyLexer.VAR; import static org.apache.groovy.parser.antlr4.GroovyLexer.VOID; import static org.apache.groovy.parser.antlr4.GroovyLexer.VOLATILE; @@ -124,7 +125,7 @@ */ public class SmartDocumentFilter extends DocumentFilter { public static final List HIGHLIGHTED_TOKEN_TYPE_LIST = Arrays.asList(AS, DEF, IN, TRAIT, THREADSAFE, - VAR, BuiltInPrimitiveType, ABSTRACT, ASSERT, BREAK, CASE, CATCH, CLASS, CONST, CONTINUE, DEFAULT, DO, + VAL, VAR, BuiltInPrimitiveType, ABSTRACT, ASSERT, BREAK, CASE, CATCH, CLASS, CONST, CONTINUE, DEFAULT, DO, ELSE, ENUM, EXTENDS, FINAL, FINALLY, FOR, IF, GOTO, IMPLEMENTS, IMPORT, INSTANCEOF, INTERFACE, NATIVE, NEW, NON_SEALED, NOT_IN, NOT_INSTANCEOF, PACKAGE, PERMITS, PRIVATE, PROTECTED, PUBLIC, RECORD, RETURN, SEALED, STATIC, STRICTFP, SUPER, SWITCH, SYNCHRONIZED,