[ 
https://issues.apache.org/jira/browse/GROOVY-11964?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18076265#comment-18076265
 ] 

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 lowering would be 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.





> 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)

Reply via email to