[
https://issues.apache.org/jira/browse/GROOVY-11964?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18076267#comment-18076267
]
ASF GitHub Bot commented on GROOVY-11964:
-----------------------------------------
paulk-asert commented on code in PR #2489:
URL: https://github.com/apache/groovy/pull/2489#discussion_r3142661609
##########
src/test/groovy/gls/statements/MultipleAssignmentDeclarationTest.groovy:
##########
@@ -226,4 +226,1326 @@ final class MultipleAssignmentDeclarationTest {
assert baz == 'baz'
'''
}
+
+ // GEP-20 tail-rest tests
+
+ @Test
+ void testTailRestFromList() {
+ assertScript '''
+ def (h, *t) = [1, 2, 3, 4]
+ assert h == 1
+ assert t == [2, 3, 4]
+ '''
+ }
+
+ @Test
+ void testTailRestWithMultipleHeads() {
+ assertScript '''
+ def (a, b, *rest) = [10, 20, 30, 40, 50]
+ assert a == 10
+ assert b == 20
+ assert rest == [30, 40, 50]
+ '''
+ }
+
+ @Test
+ void testTailRestWithTypedHead() {
+ assertScript '''
+ def (int h, *t) = [1, 2, 3]
+ assert h == 1
+ assert h instanceof Integer
+ assert t == [2, 3]
+ '''
+ }
+
+ @Test
+ void testTailRestFromEmptyList() {
+ assertScript '''
+ def (h, *t) = []
+ assert h == null
+ assert t == []
+ '''
+ }
+
+ @Test
+ void testTailRestFromString() {
+ assertScript '''
+ def (c, *cs) = "hello"
+ assert c == 'h'
+ assert cs == "ello"
+ '''
+ }
+
+ @Test
+ void testTailRestFromIterator() {
+ assertScript '''
+ def (h, t) = [1, 2, 3].iterator() // baseline: same iterator
semantics as existing
+ assert h == 1
+ assert t == 2
+
+ // tail-rest binds the advanced iterator (Path B)
+ def it = [10, 20, 30].iterator()
+ def (head, *tail) = it
+ assert head == 10
+ assert tail instanceof Iterator
+ assert tail.next() == 20
+ assert tail.next() == 30
+ assert !tail.hasNext()
+ '''
+ }
+
+ @Test
+ void testTailRestFromSet() {
+ assertScript '''
+ def s = new LinkedHashSet<>([7, 8, 9])
+ def (sh, *st) = s
+ assert sh == 7
+ def remaining = []
+ while (st.hasNext()) remaining << st.next()
+ assert remaining == [8, 9]
+ '''
+ }
+
+ @Test
+ void testTailRestFromStream() {
+ assertScript '''import java.util.stream.Stream
+ def (first, *rest) = Stream.of('a', 'b', 'c')
+ assert first == 'a'
+ assert rest instanceof Iterator
Review Comment:
In the GEP, we show these lowerings:
```
def (h, *t) = list
```
becomes:
```
// Path A — RHS supports getAt(IntRange):
def __rhs = rhs
def h = __rhs.getAt(0)
def t = __rhs.getAt(1..-1)
// Path B — RHS iterable but not range-indexable:
def __it = rhs.iterator()
def h = __it.hasNext() ? __it.next() : null
def t = __it
```
For the Stream case, the simplest implementation would just detect
`instanceof Stream` and produce a lowering something like:
```
// Path C — RHS is a Stream:
def __rhs = rhs
def __it = __rhs.iterator()
def h = __it.hasNext() ? __it.next() : null
def t = StreamSupport.stream(
Spliterators.spliteratorUnknownSize(__it, 0),
false
).onClose { __rhs.close() }
```
And this has some simplifications:
* The wrapped tail is sequential and reports no spliterator characteristics
(no SIZED, ORDERED, IMMUTABLE, etc.). Capturing the original characteristics
requires reading __rhs.spliterator() instead of __rhs.iterator() — but they're
mutually exclusive terminal-class ops, so head extraction would have to switch
to Spliterator.tryAdvance, which is implementable but adds machinery that
doesn't change semantics for the typical destructuring use case.
* Primitive streams (IntStream, LongStream, DoubleStream) fall back to the
iterator() path unless you call .boxed().
I do think the Stream use case probably still justifies this but it
increases the complexity of the mental model and the education costs in
describing the limitations. The lowering above is in some sense an
implementation detail. I will look at changing to above and we can over time
handle more cases if demand warrants it.
> Additional multi-assignment forms
> ---------------------------------
>
> Key: GROOVY-11964
> URL: https://issues.apache.org/jira/browse/GROOVY-11964
> Project: Groovy
> Issue Type: Improvement
> Reporter: Paul King
> Priority: Major
>
> Three idioms are common in modern languages but unavailable in Groovy:
> * Tail rest binding: {{def (h, *t) = list}} - separate the head from the
> remainder. Common in functional code (recursive list traversal,
> pipeline-style processing of streams and iterators).
> * Map-style destructuring: {{def (name: n, age: a) = person}} - extract named
> properties or map keys directly, without intermediate variables.
> * Rest in non-tail positions: {{def (*f, last) = list, def (l, *m, r) =
> list}} - useful when the boundaries are at the ends rather than the middle.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)