You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/08/05 16:42:00 UTC

[groovy] branch GROOVY-10666 updated (fa15e5c270 -> 272d76e9f2)

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a change to branch GROOVY-10666
in repository https://gitbox.apache.org/repos/asf/groovy.git


    omit fa15e5c270 GROOVY-5744, GROOVY-10666: multi-assign via `iterator()` or `getAt(int)`
     new 272d76e9f2 GROOVY-5744, GROOVY-10666: multi-assign via `iterator()` or `getAt(int)`

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (fa15e5c270)
            \
             N -- N -- N   refs/heads/GROOVY-10666 (272d76e9f2)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../gls/statements/MultipleAssignmentTest.groovy   |  4 +-
 src/test/gls/syntax/Gep3Test.groovy                | 89 +++++++++++-----------
 2 files changed, 46 insertions(+), 47 deletions(-)


[groovy] 01/01: GROOVY-5744, GROOVY-10666: multi-assign via `iterator()` or `getAt(int)`

Posted by em...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY-10666
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 272d76e9f22aa16ab8f2d18d1cd462596368a2fe
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Jul 31 21:07:21 2022 -0500

    GROOVY-5744, GROOVY-10666: multi-assign via `iterator()` or `getAt(int)`
---
 .../classgen/asm/BinaryExpressionHelper.java       | 172 ++++++++++++++-------
 .../groovy/classgen/asm/BytecodeVariable.java      |  65 ++++----
 .../codehaus/groovy/classgen/asm/CompileStack.java |  17 +-
 .../MultipleAssignmentDeclarationTest.groovy       |  55 ++++++-
 4 files changed, 210 insertions(+), 99 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
index e5810243ea..c6bde894b4 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BinaryExpressionHelper.java
@@ -33,7 +33,6 @@ import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.FieldExpression;
 import org.codehaus.groovy.ast.expr.ListExpression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
-import org.codehaus.groovy.ast.expr.NotExpression;
 import org.codehaus.groovy.ast.expr.PostfixExpression;
 import org.codehaus.groovy.ast.expr.PrefixExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
@@ -53,8 +52,13 @@ import org.objectweb.asm.MethodVisitor;
 import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.elvisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.notX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ternaryX;
 import static org.codehaus.groovy.syntax.Types.ASSIGN;
 import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
 import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
@@ -105,10 +109,14 @@ import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL;
 import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED;
 import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL;
 import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.DUP;
 import static org.objectweb.asm.Opcodes.GOTO;
 import static org.objectweb.asm.Opcodes.IFEQ;
 import static org.objectweb.asm.Opcodes.IFNE;
+import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
 import static org.objectweb.asm.Opcodes.INSTANCEOF;
+import static org.objectweb.asm.Opcodes.POP;
 
 public class BinaryExpressionHelper {
     // compare
@@ -133,7 +141,7 @@ public class BinaryExpressionHelper {
 
     public BinaryExpressionHelper(final WriterController wc) {
         this.controller = wc;
-        this.unaryExpressionHelper = new UnaryExpressionHelper(this.controller);
+        this.unaryExpressionHelper = new UnaryExpressionHelper(wc);
     }
 
     public WriterController getController() {
@@ -347,12 +355,11 @@ public class BinaryExpressionHelper {
     }
 
     protected void assignToArray(final Expression parent, final Expression receiver, final Expression index, final Expression rhsValueLoader, final boolean safe) {
-        // let's replace this assignment to a subscript operator with a
-        // method call
+        // let's replace this assignment to a subscript operator with a method call
         // e.g. x[5] = 10
         // -> (x, [], 5), =, 10
         // -> methodCall(x, "putAt", [5, 10])
-        ArgumentListExpression ae = new ArgumentListExpression(index,rhsValueLoader);
+        ArgumentListExpression ae = new ArgumentListExpression(index, rhsValueLoader);
         controller.getInvocationWriter().makeCall(parent, receiver, constX("putAt"), ae, InvocationWriter.invokeMethod, safe, false, false);
         controller.getOperandStack().pop();
         // return value of assignment
@@ -360,22 +367,25 @@ public class BinaryExpressionHelper {
     }
 
     public void evaluateElvisEqual(final BinaryExpression expression) {
-        Token operation = expression.getOperation();
-        BinaryExpression elvisAssignmentExpression = binX(
-                expression.getLeftExpression(),
-                Token.newSymbol(ASSIGN, operation.getStartLine(), operation.getStartColumn()),
-                new ElvisOperatorExpression(expression.getLeftExpression(), expression.getRightExpression())
+        Expression lhs = expression.getLeftExpression();
+        Expression rhs = elvisX(lhs, expression.getRightExpression());
+        BinaryExpression assignment = binX(
+                lhs,
+                Token.newSymbol(ASSIGN, expression.getOperation().getStartLine(), expression.getOperation().getStartColumn()),
+                rhs
         );
-        this.evaluateEqual(elvisAssignmentExpression, false);
+        evaluateEqual(assignment, false);
     }
 
     public void evaluateEqual(final BinaryExpression expression, final boolean defineVariable) {
         AsmClassGenerator acg = controller.getAcg();
+        MethodVisitor mv = controller.getMethodVisitor();
         CompileStack compileStack = controller.getCompileStack();
         OperandStack operandStack = controller.getOperandStack();
         Expression leftExpression = expression.getLeftExpression();
         Expression rightExpression = expression.getRightExpression();
-        boolean directAssignment = defineVariable && !(leftExpression instanceof TupleExpression);
+        boolean singleAssignment = !(leftExpression instanceof TupleExpression);
+        boolean directAssignment = defineVariable && singleAssignment; //def x=y
 
         if (directAssignment && rightExpression instanceof EmptyExpression) {
             VariableExpression ve = (VariableExpression) leftExpression;
@@ -387,9 +397,8 @@ public class BinaryExpressionHelper {
         // TODO: LHS has not been visited, it could be a variable in a closure and type chooser is not aware.
         ClassNode lhsType = controller.getTypeChooser().resolveType(leftExpression, controller.getClassNode());
         if (rightExpression instanceof ListExpression && lhsType.isArray()) {
-            ListExpression list = (ListExpression) rightExpression;
-            ArrayExpression array = new ArrayExpression(lhsType.getComponentType(), list.getExpressions());
-            array.setSourcePosition(list);
+            Expression array = new ArrayExpression(lhsType.getComponentType(), ((ListExpression) rightExpression).getExpressions());
+            array.setSourcePosition(rightExpression);
             array.visit(acg);
         } else if (rightExpression instanceof EmptyExpression) {
             loadInitValue(leftExpression.getType()); // TODO: lhsType?
@@ -430,7 +439,7 @@ public class BinaryExpressionHelper {
             rhsValueId = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
         }
         // TODO: if RHS is VariableSlotLoader already, then skip creating a new one
-        BytecodeExpression rhsValueLoader = new VariableSlotLoader(rhsType,rhsValueId,operandStack);
+        Expression rhsValueLoader = new VariableSlotLoader(rhsType, rhsValueId, operandStack);
 
         // assignment for subscript
         if (leftExpression instanceof BinaryExpression) {
@@ -444,12 +453,49 @@ public class BinaryExpressionHelper {
 
         compileStack.pushLHS(true);
 
-        if (leftExpression instanceof TupleExpression) {
-            // multiple declaration
-            TupleExpression tuple = (TupleExpression) leftExpression;
-            int i = 0;
-            for (Expression e : tuple.getExpressions()) {
-                callX(rhsValueLoader, "getAt", args(constX(i++))).visit(acg);
+        if (directAssignment) {
+            rhsValueLoader.visit(acg);
+            operandStack.remove(1);
+            compileStack.popLHS();
+            return;
+        }
+
+        if (singleAssignment) {
+            int mark = operandStack.getStackLength();
+            rhsValueLoader.visit(acg);
+            leftExpression.visit(acg);
+            operandStack.remove(operandStack.getStackLength() - mark);
+        } else { // multiple declaration or assignment
+            MethodCallExpression iterator = callX(rhsValueLoader, "iterator");
+            iterator.setImplicitThis(false);
+            iterator.visit(acg);
+
+            int iteratorId = compileStack.defineTemporaryVariable("$iter", true);
+            Expression seq = new VariableSlotLoader(iteratorId, operandStack);
+
+            MethodCallExpression hasNext = callX(seq, "hasNext");
+            hasNext.setImplicitThis(false);
+            boolX(hasNext).visit(acg);
+
+            Label done = new Label(), useGetAt = new Label();
+            Label useGetAt_noPop = operandStack.jump(IFEQ);
+
+            MethodCallExpression next = callX(seq, "next");
+            next.setImplicitThis(false);
+            next.visit(acg);
+
+            // check if first element is RHS; indicative of DGM#iterator(Object)
+            mv.visitInsn(DUP);
+            mv.visitVarInsn(ALOAD, rhsValueId);
+            mv.visitJumpInsn(IF_ACMPEQ, useGetAt);
+
+            boolean first = true;
+            for (Expression e : (TupleExpression) leftExpression) {
+                if (first) {
+                    first = false;
+                } else {
+                    ternaryX(hasNext, next, nullX()).visit(acg);
+                }
                 if (defineVariable) {
                     Variable v = (Variable) e;
                     operandStack.doGroovyCast(v);
@@ -459,19 +505,39 @@ public class BinaryExpressionHelper {
                     e.visit(acg);
                 }
             }
-        } else if (defineVariable) {
-            // single declaration
-            rhsValueLoader.visit(acg);
-            operandStack.remove(1);
-            compileStack.popLHS();
-            return;
-        } else {
-            // normal assignment
-            int mark = operandStack.getStackLength();
-            rhsValueLoader.visit(acg);
-            leftExpression.visit(acg);
-            operandStack.remove(operandStack.getStackLength() - mark);
+
+            mv.visitJumpInsn(GOTO, done);
+
+            mv.visitLabel(useGetAt);
+
+            mv.visitInsn(POP); // discard result of "rhs.iterator().next()"
+
+            mv.visitLabel(useGetAt_noPop);
+
+            int i = 0;
+            for (Expression e : (TupleExpression) leftExpression) {
+                MethodCallExpression getAt = callX(rhsValueLoader, "getAt", constX(i++, true));
+                getAt.setImplicitThis(false);
+                getAt.visit(acg);
+
+                if (defineVariable) {
+                    Variable v = (Variable) e;
+                    operandStack.doGroovyCast(v);
+                    BytecodeVariable bcv = compileStack.getVariable(v.getName());
+                    if (bcv.isHolder()) {
+                        operandStack.box();
+                        operandStack.remove(1);
+                        compileStack.createReference(bcv);
+                        continue; // Reference stored in v
+                    }
+                }
+                e.visit(acg);
+            }
+
+            mv.visitLabel(done);
+            compileStack.removeVar(iteratorId);
         }
+
         compileStack.popLHS();
 
         // return value of assignment
@@ -490,11 +556,10 @@ public class BinaryExpressionHelper {
     }
 
     protected void evaluateCompareExpression(final MethodCaller compareMethod, final BinaryExpression expression) {
-        ClassNode classNode = controller.getClassNode();
         Expression leftExp = expression.getLeftExpression();
         Expression rightExp = expression.getRightExpression();
-        ClassNode leftType = controller.getTypeChooser().resolveType(leftExp, classNode);
-        ClassNode rightType = controller.getTypeChooser().resolveType(rightExp, classNode);
+        ClassNode  leftType = controller.getTypeChooser().resolveType(leftExp, controller.getClassNode());
+        ClassNode  rightType = controller.getTypeChooser().resolveType(rightExp, controller.getClassNode());
 
         boolean done = false;
         if (ClassHelper.isPrimitiveType(leftType) && ClassHelper.isPrimitiveType(rightType)) {
@@ -613,12 +678,13 @@ public class BinaryExpressionHelper {
         MethodCallExpression putAt = callX(receiver, "putAt", args(subscript, ret));
 
         AsmClassGenerator acg = controller.getAcg();
+        CompileStack compileStack = controller.getCompileStack();
+        OperandStack operandStack = controller.getOperandStack();
+
         putAt.visit(acg);
-        OperandStack os = controller.getOperandStack();
-        os.pop();
-        os.load(ret.getType(), ret.getIndex());
+        operandStack.pop();
+        operandStack.load(ret.getType(), ret.getIndex());
 
-        CompileStack compileStack = controller.getCompileStack();
         compileStack.removeVar(ret.getIndex());
         compileStack.removeVar(subscript.getIndex());
         compileStack.removeVar(receiver.getIndex());
@@ -657,8 +723,8 @@ public class BinaryExpressionHelper {
 
     private void evaluateNotInstanceof(final BinaryExpression expression) {
         unaryExpressionHelper.writeNotExpression(
-                new NotExpression(
-                        new BinaryExpression(
+                notX(
+                        binX(
                                 expression.getLeftExpression(),
                                 GeneralUtils.INSTANCEOF,
                                 expression.getRightExpression()
@@ -821,7 +887,15 @@ public class BinaryExpressionHelper {
         // would be a.getAt(1).next() for the rhs, "lhs" code is a.putAt(1, rhs)
     }
 
-    private void evaluateElvisOperatorExpression(final ElvisOperatorExpression expression) {
+    public void evaluateTernary(final TernaryExpression expression) {
+        if (expression instanceof ElvisOperatorExpression) {
+            evaluateElvisExpression(expression);
+        } else {
+            evaluateTernaryExpression(expression);
+        }
+    }
+
+    private void evaluateElvisExpression(final TernaryExpression expression) {
         MethodVisitor mv = controller.getMethodVisitor();
         CompileStack compileStack = controller.getCompileStack();
         OperandStack operandStack = controller.getOperandStack();
@@ -876,7 +950,7 @@ public class BinaryExpressionHelper {
         operandStack.replace(common, 2);
     }
 
-    private void evaluateNormalTernary(final TernaryExpression expression) {
+    private void evaluateTernaryExpression(final TernaryExpression expression) {
         MethodVisitor mv = controller.getMethodVisitor();
         TypeChooser typeChooser = controller.getTypeChooser();
         OperandStack operandStack = controller.getOperandStack();
@@ -916,12 +990,4 @@ public class BinaryExpressionHelper {
         mv.visitLabel(l1);
         operandStack.replace(common, 2);
     }
-
-    public void evaluateTernary(final TernaryExpression expression) {
-        if (expression instanceof ElvisOperatorExpression) {
-            evaluateElvisOperatorExpression((ElvisOperatorExpression) expression);
-        } else {
-            evaluateNormalTernary(expression);
-        }
-    }
 }
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java
index 0ec751a374..4c4c5dda5f 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeVariable.java
@@ -34,28 +34,34 @@ public class BytecodeVariable {
     private ClassNode type;
     private String name;
     private final int prevCurrent;
+    private boolean dynamicTyped;
     private boolean holder;
 
     // br for setting on the LocalVariableTable in the class file
     // these fields should probably go to jvm Operand class
-    private Label startLabel = null;
-    private Label endLabel = null;
-    private boolean dynamicTyped;
+    private Label startLabel;
+    private Label endLabel;
 
-    private BytecodeVariable(){
+    private BytecodeVariable() {
+        index = 0;
+        prevCurrent = 0;
         dynamicTyped = true;
-        index=0;
-        holder=false;
-        prevCurrent=0;
     }
 
-    public BytecodeVariable(int index, ClassNode type, String name, int prevCurrent) {
+    public BytecodeVariable(final int index, final ClassNode type, final String name, final int prevCurrent) {
         this.index = index;
         this.type = type;
         this.name = name;
         this.prevCurrent = prevCurrent;
     }
 
+    /**
+     * @return the stack index for this variable
+     */
+    public int getIndex() {
+        return index;
+    }
+
     public String getName() {
         return name;
     }
@@ -64,11 +70,21 @@ public class BytecodeVariable {
         return type;
     }
 
-    /**
-     * @return the stack index for this variable
-     */
-    public int getIndex() {
-        return index;
+    public void setType(final ClassNode type) {
+        this.type = type;
+        dynamicTyped = dynamicTyped || ClassHelper.isDynamicTyped(type);
+    }
+
+    public int getPrevIndex() {
+        return prevCurrent;
+    }
+
+    public boolean isDynamicTyped() {
+        return dynamicTyped;
+    }
+
+    public void setDynamicTyped(final boolean b) {
+        dynamicTyped = b;
     }
 
     /**
@@ -78,7 +94,7 @@ public class BytecodeVariable {
         return holder;
     }
 
-    public void setHolder(boolean holder) {
+    public void setHolder(final boolean holder) {
         this.holder = holder;
     }
 
@@ -86,7 +102,7 @@ public class BytecodeVariable {
         return startLabel;
     }
 
-    public void setStartLabel(Label startLabel) {
+    public void setStartLabel(final Label startLabel) {
         this.startLabel = startLabel;
     }
 
@@ -94,7 +110,7 @@ public class BytecodeVariable {
         return endLabel;
     }
 
-    public void setEndLabel(Label endLabel) {
+    public void setEndLabel(final Label endLabel) {
         this.endLabel = endLabel;
     }
 
@@ -102,21 +118,4 @@ public class BytecodeVariable {
     public String toString() {
         return name + "(index=" + index + ",type=" + type + ",holder="+holder+")";
     }
-
-    public void setType(ClassNode type) {
-        this.type = type;
-        dynamicTyped |= ClassHelper.isDynamicTyped(type);
-    }
-
-    public void setDynamicTyped(boolean b) {
-        dynamicTyped = b;
-    }
-
-    public boolean isDynamicTyped() {
-        return dynamicTyped;
-    }
-
-    public int getPrevIndex() {
-        return prevCurrent;
-    }
 }
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
index 64a8ce05a3..72209f985c 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/CompileStack.java
@@ -235,9 +235,12 @@ public class CompileStack {
         inSpecialConstructorCall = element.inSpecialConstructorCall;
     }
 
-    public void removeVar(final int tempIndex) {
+    /**
+     * Indicates that the specified temporary variable is no longer used.
+     */
+    public void removeVar(final int variableIndex) {
         BytecodeVariable head = temporaryVariables.removeFirst();
-        if (head.getIndex() != tempIndex) {
+        if (head.getIndex() != variableIndex) {
             temporaryVariables.addFirst(head);
             MethodNode methodNode = controller.getMethodNode();
             if (methodNode == null) {
@@ -246,7 +249,7 @@ public class CompileStack {
             throw new GroovyBugError(
                     "In method "+ (methodNode!=null?methodNode.getText():"<unknown>") + ", " +
                     "CompileStack#removeVar: tried to remove a temporary " +
-                    "variable with index "+ tempIndex + " in wrong order. " +
+                    "variable with index " + variableIndex + " in wrong order. " +
                     "Current temporary variables=" + temporaryVariables);
         }
     }
@@ -266,9 +269,9 @@ public class CompileStack {
     }
 
     /**
-     * creates a temporary variable.
+     * Creates a temporary variable.
      *
-     * @param var defines type and name
+     * @param var specifies name and type
      * @param store defines if the toplevel argument of the stack should be stored
      * @return the index used for this temporary variable
      */
@@ -276,7 +279,7 @@ public class CompileStack {
         return defineTemporaryVariable(var.getName(), var.getType(),store);
     }
 
-    public BytecodeVariable getVariable(final String variableName ) {
+    public BytecodeVariable getVariable(final String variableName) {
         return getVariable(variableName, true);
     }
 
@@ -627,7 +630,7 @@ public class CompileStack {
         nextVariableIndex = localVariableOffset;
     }
 
-    private void createReference(final BytecodeVariable reference) {
+    void createReference(final BytecodeVariable reference) {
         MethodVisitor mv = controller.getMethodVisitor();
         mv.visitTypeInsn(NEW, "groovy/lang/Reference");
         mv.visitInsn(DUP_X1);
diff --git a/src/test/gls/statements/MultipleAssignmentDeclarationTest.groovy b/src/test/gls/statements/MultipleAssignmentDeclarationTest.groovy
index 8ae6744b3e..b2ddd6aeeb 100644
--- a/src/test/gls/statements/MultipleAssignmentDeclarationTest.groovy
+++ b/src/test/gls/statements/MultipleAssignmentDeclarationTest.groovy
@@ -18,7 +18,6 @@
  */
 package gls.statements
 
-import groovy.test.NotYetImplemented
 import org.junit.Test
 
 import static groovy.test.GroovyAssert.assertScript
@@ -79,10 +78,10 @@ final class MultipleAssignmentDeclarationTest {
     }
 
     @Test
-    void testNestedScope() {
+    void testNestedScope1() {
         assertScript '''import static groovy.test.GroovyAssert.shouldFail
 
-            def c = {
+            def c = { ->
                 def (i,j) = [1,2]
                 assert i == 1
                 assert j == 2
@@ -107,6 +106,33 @@ final class MultipleAssignmentDeclarationTest {
         '''
     }
 
+    @Test
+    void testNestedScope2() {
+        assertScript '''
+            class C {
+                int m() {
+                    def (i,j) = [1,2]
+                    assert i == 1
+                    assert j == 2
+
+                    def x = { ->
+                        assert i == 1
+                        assert j == 2
+
+                        i = 3
+                        assert i == 3
+                    }
+                    x()
+
+                    assert i == 3
+                    return j
+                }
+            }
+            int n = new C().m()
+            assert n == 2
+        '''
+    }
+
     @Test
     void testMultiAssignChain() {
         assertScript '''
@@ -117,7 +143,24 @@ final class MultipleAssignmentDeclarationTest {
         '''
     }
 
-    @NotYetImplemented @Test // GROOVY-5744
+    @Test
+    void testMultiAssignFromObject() {
+        shouldFail MissingMethodException, '''
+            def obj = new Object()
+            def (x) = obj
+        '''
+    }
+
+    @Test
+    void testMultiAssignFromCalendar() {
+        assertScript '''
+            def (_, y, m) = Calendar.instance
+            assert y >= 2022
+            assert m in 0..11
+        '''
+    }
+
+    @Test // GROOVY-5744
     void testMultiAssignFromIterator() {
         assertScript '''
             def list = [1,2,3]
@@ -129,7 +172,7 @@ final class MultipleAssignmentDeclarationTest {
         '''
     }
 
-    @NotYetImplemented @Test // GROOVY-10666
+    @Test // GROOVY-10666
     void testMultiAssignFromIterable() {
         assertScript '''
             class MySet {
@@ -172,7 +215,7 @@ final class MultipleAssignmentDeclarationTest {
         '''
     }
 
-    @NotYetImplemented @Test // GROOVY-10666
+    @Test // GROOVY-10666
     void testMultiAssignFromJavaStream() {
         assertScript '''import java.util.stream.Stream