Skip to content

Commit cd3296b

Browse files
committed
GEP-16/GEP-20 overlap cleanup now that both are merged.
1 parent 30908a5 commit cd3296b

3 files changed

Lines changed: 95 additions & 10 deletions

File tree

src/antlr/GroovyParser.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,11 +584,11 @@ typeNamePairs
584584
;
585585

586586
typeNamePair
587-
: (DEF | VAR | type)? MUL? variableDeclaratorId
587+
: (DEF | VAL | VAR | type)? MUL? variableDeclaratorId
588588
;
589589

590590
keyedPair
591-
: key=identifier COLON (DEF | VAR | type)? variableDeclaratorId
591+
: key=identifier COLON (DEF | VAL | VAR | type)? variableDeclaratorId
592592
;
593593

594594
variableNames

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,13 +2228,17 @@ public VariableExpression visitTypeNamePair(final TypeNamePairContext ctx) {
22282228
boolean isRest = asBoolean(ctx.MUL());
22292229
// GEP-20: typed rest (e.g. `def (h, List<Integer> *t) = list`) is accepted; the
22302230
// declared container type is honoured by static type checking and runtime coercion.
2231-
// `def` and `var` are also accepted in place of a type (equivalent to omitting it),
2232-
// for symmetry with switch case patterns and the bracket-form declaration grammar.
2231+
// `def`, `val`, and `var` are also accepted in place of a type (equivalent to omitting
2232+
// it), for symmetry with switch case patterns and the bracket-form declaration grammar.
2233+
// `val` additionally marks the binding as final.
22332234
VariableExpression ve = configureAST(
22342235
new VariableExpression(
22352236
this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
2236-
binderType(ctx.DEF(), ctx.VAR(), ctx.type())),
2237+
binderType(ctx.DEF(), ctx.VAL(), ctx.VAR(), ctx.type())),
22372238
ctx);
2239+
if (asBoolean(ctx.VAL())) {
2240+
ve.setModifiers(ve.getModifiers() | Opcodes.ACC_FINAL);
2241+
}
22382242
if (isRest) {
22392243
ve.putNodeMetaData(MultipleAssignmentMetadata.REST_BINDING, Boolean.TRUE);
22402244
}
@@ -2246,15 +2250,18 @@ public VariableExpression visitKeyedPair(final KeyedPairContext ctx) {
22462250
VariableExpression ve = configureAST(
22472251
new VariableExpression(
22482252
this.visitVariableDeclaratorId(ctx.variableDeclaratorId()).getName(),
2249-
binderType(ctx.DEF(), ctx.VAR(), ctx.type())),
2253+
binderType(ctx.DEF(), ctx.VAL(), ctx.VAR(), ctx.type())),
22502254
ctx);
2255+
if (asBoolean(ctx.VAL())) {
2256+
ve.setModifiers(ve.getModifiers() | Opcodes.ACC_FINAL);
2257+
}
22512258
ve.putNodeMetaData(MultipleAssignmentMetadata.MAP_KEY, this.visitIdentifier(ctx.key));
22522259
return ve;
22532260
}
22542261

2255-
/** GEP-20: resolve a binder's declared type — `def`/`var` produce the dynamic type, same as omitting a type. */
2256-
private ClassNode binderType(final TerminalNode defNode, final TerminalNode varNode, final TypeContext typeCtx) {
2257-
if (asBoolean(defNode) || asBoolean(varNode)) return ClassHelper.dynamicType();
2262+
/** GEP-20: resolve a binder's declared type — `def`/`val`/`var` produce the dynamic type, same as omitting a type. */
2263+
private ClassNode binderType(final TerminalNode defNode, final TerminalNode valNode, final TerminalNode varNode, final TypeContext typeCtx) {
2264+
if (asBoolean(defNode) || asBoolean(valNode) || asBoolean(varNode)) return ClassHelper.dynamicType();
22582265
return this.visitType(typeCtx);
22592266
}
22602267

src/test/groovy/gls/statements/MultipleAssignmentDeclarationTest.groovy

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1572,8 +1572,9 @@ final class MultipleAssignmentDeclarationTest {
15721572
'''
15731573
}
15741574

1575-
// GEP-20 def/var binders: accepted in place of a type, equivalent to omitting it.
1575+
// GEP-20 def/val/var binders: accepted in place of a type, equivalent to omitting it.
15761576
// Provided for symmetry with switch case patterns and the GEP-19 bracket-form grammar.
1577+
// `val` additionally marks the binding as final.
15771578

15781579
@Test
15791580
void testDefBinder_positional() {
@@ -1591,6 +1592,14 @@ final class MultipleAssignmentDeclarationTest {
15911592
'''
15921593
}
15931594

1595+
@Test
1596+
void testValBinder_positional() {
1597+
assertScript '''
1598+
def (val a, val b) = [1, 2]
1599+
assert a == 1 && b == 2
1600+
'''
1601+
}
1602+
15941603
@Test
15951604
void testMixedDefVarTypedBinder() {
15961605
assertScript '''
@@ -1599,6 +1608,14 @@ final class MultipleAssignmentDeclarationTest {
15991608
'''
16001609
}
16011610

1611+
@Test
1612+
void testMixedDefValVarTypedBinder() {
1613+
assertScript '''
1614+
def (def a, val b, var c, int d) = [1, 2, 3, 4]
1615+
assert a == 1 && b == 2 && c == 3 && d == 4
1616+
'''
1617+
}
1618+
16021619
@Test
16031620
void testVarBinder_withRest() {
16041621
assertScript '''
@@ -1608,6 +1625,15 @@ final class MultipleAssignmentDeclarationTest {
16081625
'''
16091626
}
16101627

1628+
@Test
1629+
void testValBinder_withRest() {
1630+
assertScript '''
1631+
def (val h, *t) = [1, 2, 3]
1632+
assert h == 1
1633+
assert t == [2, 3]
1634+
'''
1635+
}
1636+
16111637
@Test
16121638
void testVarBinder_mapStyle() {
16131639
assertScript '''
@@ -1616,6 +1642,14 @@ final class MultipleAssignmentDeclarationTest {
16161642
'''
16171643
}
16181644

1645+
@Test
1646+
void testValBinder_mapStyle() {
1647+
assertScript '''
1648+
def (name: val n, age: val a) = [name: 'X', age: 9]
1649+
assert n == 'X' && a == 9
1650+
'''
1651+
}
1652+
16191653
@Test
16201654
void testVarBinder_CompileStatic_listLiteral() {
16211655
assertScript '''import groovy.transform.CompileStatic
@@ -1628,6 +1662,18 @@ final class MultipleAssignmentDeclarationTest {
16281662
'''
16291663
}
16301664

1665+
@Test
1666+
void testValBinder_CompileStatic_listLiteral() {
1667+
assertScript '''import groovy.transform.CompileStatic
1668+
@CompileStatic
1669+
def go() {
1670+
def (val a, val b) = [10, 20]
1671+
assert a == 10 && b == 20
1672+
}
1673+
go()
1674+
'''
1675+
}
1676+
16311677
@Test
16321678
void testVarBinder_CompileStatic_mapStyle() {
16331679
assertScript '''import groovy.transform.CompileStatic
@@ -1640,6 +1686,38 @@ final class MultipleAssignmentDeclarationTest {
16401686
'''
16411687
}
16421688

1689+
@Test
1690+
void testValBinder_CompileStatic_mapStyle() {
1691+
assertScript '''import groovy.transform.CompileStatic
1692+
@CompileStatic
1693+
def go() {
1694+
def (name: val n) = [name: 'Z']
1695+
assert n == 'Z'
1696+
}
1697+
go()
1698+
'''
1699+
}
1700+
1701+
@Test // GROOVY-11977: val inner binder makes only that name final
1702+
void testValBinder_finality_perBinder() {
1703+
def e = shouldFail '''
1704+
def (val a, var b) = [1, 2]
1705+
b = 99 // OK
1706+
a = 99 // rejected
1707+
'''
1708+
assert e.message.contains('final')
1709+
}
1710+
1711+
@Test // GROOVY-11977: val inner binder under map-style enforces finality
1712+
void testValBinder_mapStyle_finality() {
1713+
def e = shouldFail '''
1714+
def (name: val n, age: var a) = [name: 'X', age: 9]
1715+
a = 10 // OK
1716+
n = 'Y' // rejected
1717+
'''
1718+
assert e.message.contains('final')
1719+
}
1720+
16431721
// GEP-20 edge cases: empty Map literal, null RHS, Map missing-key with primitive
16441722
// vs Object binders, classic for-init multi-assignment, method-returning-typed.
16451723

0 commit comments

Comments
 (0)