Skip to content

Commit 7203ff7

Browse files
authored
GROOVY-11977: val type placeholder for final declarations (#2464)
1 parent 661e6cf commit 7203ff7

13 files changed

Lines changed: 162 additions & 20 deletions

File tree

src/antlr/GroovyLexer.g4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ THROW : 'throw';
483483
THROWS : 'throws';
484484
TRANSIENT : 'transient';
485485
TRY : 'try';
486+
VAL : 'val';
486487
VAR : 'var';
487488
VOID : 'void';
488489
VOLATILE : 'volatile';

src/antlr/GroovyParser.g4

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ modifier
134134
| TRANSIENT
135135
| VOLATILE
136136
| DEF
137+
| VAL
137138
| VAR
138139
)
139140
;
@@ -175,6 +176,7 @@ variableModifier
175176
: annotation
176177
| m=( FINAL
177178
| DEF
179+
| VAL
178180
| VAR
179181
// Groovy supports declaring local variables as instance/class fields,
180182
// e.g. import groovy.transform.*; @Field static List awe = [1, 2, 3]
@@ -706,7 +708,7 @@ enhancedForControl
706708
;
707709

708710
indexVariable
709-
: (BuiltInPrimitiveType | DEF | VAR)? identifier
711+
: (BuiltInPrimitiveType | DEF | VAL | VAR)? identifier
710712
;
711713

712714
originalForControl
@@ -1254,6 +1256,7 @@ identifier
12541256
| RECORD
12551257
| SEALED
12561258
| TRAIT
1259+
| VAL
12571260
| VAR
12581261
| YIELD
12591262
;
@@ -1312,6 +1315,7 @@ keywords
13121315
| TRAIT
13131316
| THREADSAFE
13141317
| TRY
1318+
| VAL
13151319
| VAR
13161320
| VOLATILE
13171321
| WHILE

src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,8 +1225,8 @@ public ClassNode visitTypeDeclaration(final TypeDeclarationContext ctx) {
12251225
public ClassNode visitClassDeclaration(final ClassDeclarationContext ctx) {
12261226
String packageName = Optional.ofNullable(this.moduleNode.getPackageName()).orElse("");
12271227
String className = this.visitIdentifier(ctx.identifier());
1228-
if ("var".equals(className)) {
1229-
throw createParsingFailedException("var cannot be used for type declarations", ctx.identifier());
1228+
if ("var".equals(className) || "val".equals(className)) {
1229+
throw createParsingFailedException(className + " cannot be used for type declarations", ctx.identifier());
12301230
}
12311231

12321232
boolean isAnnotation = asBoolean(ctx.AT());
@@ -1747,8 +1747,8 @@ public MethodNode visitCompactConstructorDeclaration(final CompactConstructorDec
17471747
throw createParsingFailedException("Only record can have compact constructor", ctx);
17481748
}
17491749

1750-
if (new ModifierManager(this, ctx.getNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS)).containsAny(VAR)) {
1751-
throw createParsingFailedException("var cannot be used for compact constructor declaration", ctx);
1750+
if (new ModifierManager(this, ctx.getNodeMetaData(COMPACT_CONSTRUCTOR_DECLARATION_MODIFIERS)).containsAny(VAL, VAR)) {
1751+
throw createParsingFailedException("val/var cannot be used for compact constructor declaration", ctx);
17521752
}
17531753

17541754
String methodName = this.visitMethodName(ctx.methodName());
@@ -1785,8 +1785,8 @@ public void visitPropertyExpression(final PropertyExpression expression) {
17851785
public MethodNode visitMethodDeclaration(final MethodDeclarationContext ctx) {
17861786
ModifierManager modifierManager = createModifierManager(ctx);
17871787

1788-
if (modifierManager.containsAny(VAR)) {
1789-
throw createParsingFailedException("var cannot be used for method declarations", ctx);
1788+
if (modifierManager.containsAny(VAL, VAR)) {
1789+
throw createParsingFailedException("val/var cannot be used for method return types", ctx);
17901790
}
17911791

17921792
String methodName = this.visitMethodName(ctx.methodName());
@@ -4691,7 +4691,7 @@ private boolean isSyntheticPublic(final boolean isAnnotationDeclaration, final b
46914691
return true;
46924692
}
46934693

4694-
if (hasReturnType && (modifierManager.containsAny(DEF, VAR))) {
4694+
if (hasReturnType && (modifierManager.containsAny(DEF, VAL, VAR))) {
46954695
return true;
46964696
}
46974697

src/main/java/org/codehaus/groovy/ast/ModifierNode.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static org.apache.groovy.parser.antlr4.GroovyParser.STRICTFP;
3939
import static org.apache.groovy.parser.antlr4.GroovyParser.SYNCHRONIZED;
4040
import static org.apache.groovy.parser.antlr4.GroovyParser.TRANSIENT;
41+
import static org.apache.groovy.parser.antlr4.GroovyParser.VAL;
4142
import static org.apache.groovy.parser.antlr4.GroovyParser.VAR;
4243
import static org.apache.groovy.parser.antlr4.GroovyParser.VOLATILE;
4344
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
@@ -56,6 +57,7 @@ public class ModifierNode extends ASTNode {
5657
public static final Map<Integer, Integer> MODIFIER_OPCODE_MAP = Maps.of(
5758
ANNOTATION_TYPE, 0,
5859
DEF, 0,
60+
VAL, Opcodes.ACC_FINAL,
5961
VAR, 0,
6062

6163
NATIVE, Opcodes.ACC_NATIVE,
@@ -129,7 +131,11 @@ public boolean isAnnotation() {
129131
}
130132

131133
public boolean isDef() {
132-
return Objects.equals(DEF, this.type) || Objects.equals(VAR, this.type);
134+
return Objects.equals(DEF, this.type) || Objects.equals(VAL, this.type) || Objects.equals(VAR, this.type);
135+
}
136+
137+
public boolean isVal() {
138+
return Objects.equals(VAL, this.type);
133139
}
134140

135141
public Integer getType() {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
// basic val declaration
21+
val name = "Groovy"
22+
assert "Groovy" == name
23+
24+
// val as closure parameter name
25+
[1, 2, 3].each { val ->
26+
assert 0 < val && val < 4
27+
}
28+
29+
// val as variable name (contextual keyword)
30+
val val = "val variable name"
31+
assert "val variable name" == val
32+
33+
// val in map key
34+
def m = [val: 42]
35+
assert m.val == 42
36+
37+
// val with different types
38+
val x = 42
39+
assert x == 42
40+
val s = "hello"
41+
assert s.class == String
42+
43+
// shallow finality - mutation OK
44+
val list = [1, 2, 3]
45+
list << 4
46+
assert list == [1, 2, 3, 4]
47+
48+
// final val is redundant but works
49+
final val y = 10
50+
assert y == 10
51+
52+
// for loop with val
53+
for (val i in [1, 2, 3]) { assert i > 0 }
54+
55+
// GString interpolation
56+
val g = 99
57+
assert "$g" == "99"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
class val {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
val someMethod() {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
val x = 1
21+
x = 2

src/test/groovy/bugs/Groovy5358.groovy

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,25 @@ final class Groovy5358 {
7171
void testSetPropertyOverrides() {
7272
assertScript '''
7373
class FooWorksAsMap {
74-
def val
74+
def stored
7575
void setProperty(String name, value) {
76-
val = "OK:FooWorksAsMap.$value"
76+
stored = "OK:FooWorksAsMap.$value"
7777
}
7878
}
7979
class BarWorksAsMap {
80-
def val
80+
def stored
8181
}
8282
@Category(BarWorksAsMap) class C {
8383
void setProperty(String name, value) {
84-
setVal("OK:BarWorksAsMap.$value")
84+
setStored("OK:BarWorksAsMap.$value")
8585
}
8686
}
8787
BarWorksAsMap.mixin C
8888
class BazWorksAsMap {
89-
def val
89+
def stored
9090
}
9191
BazWorksAsMap.metaClass.setProperty = { String name, value ->
92-
setVal("OK:BazWorksAsMap.$value")
92+
setStored("OK:BazWorksAsMap.$value")
9393
}
9494
9595
def objects = [
@@ -99,10 +99,10 @@ final class Groovy5358 {
9999
[:]
100100
]
101101
for (def obj in objects) {
102-
def which = "${obj.getClass().getSimpleName()}.val"
102+
def which = "${obj.getClass().getSimpleName()}.stored"
103103
try {
104-
obj.val = which.startsWith('LinkedHashMap') ? "OK:LinkedHashMap.bar" : 'bar'
105-
assert obj.val.startsWith('OK:') : "$which -> $obj.val"
104+
obj.stored = which.startsWith('LinkedHashMap') ? "OK:LinkedHashMap.bar" : 'bar'
105+
assert obj.stored.startsWith('OK:') : "$which -> $obj.stored"
106106
} catch (any) {
107107
assert false : "$which -> FAIL:$any"
108108
}

src/test/groovy/groovy/transform/stc/LambdaTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ final class LambdaTest {
817817
this.val = v
818818
}
819819
String toString() {
820-
val as String
820+
this.val as String
821821
}
822822
def <Out> Value<Out> replace(Supplier<Out> supplier) {
823823
new Value<>(supplier.get())

0 commit comments

Comments
 (0)