This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch GROOVY_4_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 4b247376a4b1f856b2116abbf8ce27ce7abe5633 Author: Eric Milles <[email protected]> AuthorDate: Wed Feb 9 12:05:11 2022 -0600 GROOVY-10476: Stream provides iterator() but does not implement Iterable --- .../java/org/codehaus/groovy/ast/ClassHelper.java | 2 + .../asm/sc/StaticTypesStatementWriter.java | 140 +++++++++------------ .../transform/stc/StaticTypeCheckingVisitor.java | 37 +++--- .../groovy/transform/stc/MethodCallsSTCTest.groovy | 13 +- 4 files changed, 91 insertions(+), 101 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java index c41dfbf..95acea2 100644 --- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java +++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java @@ -71,6 +71,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Stream; /** * Helper for {@link ClassNode} and classes handling them. Contains a set of @@ -151,6 +152,7 @@ public class ClassHelper { Enum_Type = makeWithoutCaching(Enum.class), CLASS_Type = makeWithoutCaching(Class.class), TUPLE_TYPE = makeWithoutCaching(Tuple.class), + STREAM_TYPE = makeWithoutCaching(Stream.class), ITERABLE_TYPE = makeWithoutCaching(Iterable.class), REFERENCE_TYPE = makeWithoutCaching(Reference.class), COLLECTION_TYPE = makeWithoutCaching(Collection.class), diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java index cc02e8f..69c09c3 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -20,34 +20,25 @@ package org.codehaus.groovy.classgen.asm.sc; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.ForStatement; +import org.codehaus.groovy.ast.tools.GeneralUtils; import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.classgen.asm.BytecodeVariable; import org.codehaus.groovy.classgen.asm.CompileStack; import org.codehaus.groovy.classgen.asm.MethodCaller; import org.codehaus.groovy.classgen.asm.OperandStack; import org.codehaus.groovy.classgen.asm.StatementWriter; -import org.codehaus.groovy.classgen.asm.TypeChooser; -import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import java.util.Enumeration; +import java.util.Objects; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveByte; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveChar; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveDouble; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveFloat; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveInt; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveLong; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveShort; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ARRAYLENGTH; @@ -72,65 +63,60 @@ import static org.objectweb.asm.Opcodes.SALOAD; */ public class StaticTypesStatementWriter extends StatementWriter { - private static final ClassNode ITERABLE_CLASSNODE = ClassHelper.make(Iterable.class); private static final ClassNode ENUMERATION_CLASSNODE = ClassHelper.make(Enumeration.class); private static final MethodCaller ENUMERATION_NEXT_METHOD = MethodCaller.newInterface(Enumeration.class, "nextElement"); private static final MethodCaller ENUMERATION_HASMORE_METHOD = MethodCaller.newInterface(Enumeration.class, "hasMoreElements"); - public StaticTypesStatementWriter(StaticTypesWriterController controller) { + public StaticTypesStatementWriter(final StaticTypesWriterController controller) { super(controller); } @Override - public void writeBlockStatement(BlockStatement statement) { + public void writeBlockStatement(final BlockStatement statement) { controller.switchToFastPath(); super.writeBlockStatement(statement); controller.switchToSlowPath(); } + //-------------------------------------------------------------------------- + @Override protected void writeForInLoop(final ForStatement loop) { - controller.getAcg().onLineNumber(loop,"visitForLoop"); + controller.getAcg().onLineNumber(loop, "visitForLoop"); writeStatementLabel(loop); CompileStack compileStack = controller.getCompileStack(); - MethodVisitor mv = controller.getMethodVisitor(); OperandStack operandStack = controller.getOperandStack(); compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabels()); - // Identify type of collection - TypeChooser typeChooser = controller.getTypeChooser(); + // identify type of collection Expression collectionExpression = loop.getCollectionExpression(); - ClassNode collectionType = typeChooser.resolveType(collectionExpression, controller.getClassNode()); + ClassNode collectionType = controller.getTypeChooser().resolveType(collectionExpression, controller.getClassNode()); + + int mark = operandStack.getStackLength(); Parameter loopVariable = loop.getVariable(); - int size = operandStack.getStackLength(); if (collectionType.isArray() && loopVariable.getOriginType().equals(collectionType.getComponentType())) { - writeOptimizedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); - } else if (ENUMERATION_CLASSNODE.equals(collectionType)) { + writeOptimizedForEachLoop(loop, loopVariable, collectionExpression, collectionType); + } else if (GeneralUtils.isOrImplements(collectionType, ENUMERATION_CLASSNODE)) { writeEnumerationBasedForEachLoop(loop, collectionExpression, collectionType); } else { writeIteratorBasedForEachLoop(loop, collectionExpression, collectionType); } - operandStack.popDownTo(size); + operandStack.popDownTo(mark); compileStack.pop(); } - private void writeOptimizedForEachLoop( - CompileStack compileStack, - OperandStack operandStack, - MethodVisitor mv, - ForStatement loop, - Expression collectionExpression, - ClassNode collectionType, - Parameter loopVariable) { - BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + private void writeOptimizedForEachLoop(final ForStatement loop, final Parameter loopVariable, final Expression collectionExpression, final ClassNode collectionType) { + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); + AsmClassGenerator acg = controller.getAcg(); + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); Label continueLabel = compileStack.getContinueLabel(); Label breakLabel = compileStack.getBreakLabel(); - AsmClassGenerator acg = controller.getAcg(); - // load array on stack collectionExpression.visit(acg); mv.visitInsn(DUP); @@ -155,9 +141,9 @@ public class StaticTypesStatementWriter extends StatementWriter { mv.visitJumpInsn(IF_ICMPGE, breakLabel); // get array element - loadFromArray(mv, variable, array, loopIdx); + loadFromArray(mv, operandStack, variable, array, loopIdx); - // $idx++ + // $idx += 1 mv.visitIincInsn(loopIdx, 1); // loop body @@ -172,27 +158,24 @@ public class StaticTypesStatementWriter extends StatementWriter { compileStack.removeVar(array); } - private void loadFromArray(MethodVisitor mv, BytecodeVariable variable, int array, int iteratorIdx) { - OperandStack os = controller.getOperandStack(); + private static void loadFromArray(final MethodVisitor mv, final OperandStack os, final BytecodeVariable variable, final int array, final int index) { mv.visitVarInsn(ALOAD, array); - mv.visitVarInsn(ILOAD, iteratorIdx); - + mv.visitVarInsn(ILOAD, index); ClassNode varType = variable.getType(); - - if (isPrimitiveType(varType)) { - if (isPrimitiveInt(varType)) { + if (ClassHelper.isPrimitiveType(varType)) { + if (ClassHelper.isPrimitiveInt(varType)) { mv.visitInsn(IALOAD); - } else if (isPrimitiveLong(varType)) { + } else if (ClassHelper.isPrimitiveLong(varType)) { mv.visitInsn(LALOAD); - } else if (isPrimitiveByte(varType) || isPrimitiveBoolean(varType)) { + } else if (ClassHelper.isPrimitiveByte(varType) || ClassHelper.isPrimitiveBoolean(varType)) { mv.visitInsn(BALOAD); - } else if (isPrimitiveChar(varType)) { + } else if (ClassHelper.isPrimitiveChar(varType)) { mv.visitInsn(CALOAD); - } else if (isPrimitiveShort(varType)) { + } else if (ClassHelper.isPrimitiveShort(varType)) { mv.visitInsn(SALOAD); - } else if (isPrimitiveFloat(varType)) { + } else if (ClassHelper.isPrimitiveFloat(varType)) { mv.visitInsn(FALOAD); - } else if (isPrimitiveDouble(varType)) { + } else if (ClassHelper.isPrimitiveDouble(varType)) { mv.visitInsn(DALOAD); } } else { @@ -202,46 +185,19 @@ public class StaticTypesStatementWriter extends StatementWriter { os.storeVar(variable); } - private void writeIteratorBasedForEachLoop( - ForStatement loop, - Expression collectionExpression, - ClassNode collectionType) { - - if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(collectionType, ITERABLE_CLASSNODE)) { - MethodCallExpression iterator = new MethodCallExpression(collectionExpression, "iterator", new ArgumentListExpression()); - iterator.setMethodTarget(collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY)); - iterator.setImplicitThis(false); - iterator.visit(controller.getAcg()); - } else { - collectionExpression.visit(controller.getAcg()); - controller.getMethodVisitor().visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "iterator", "(Ljava/lang/Object;)Ljava/util/Iterator;", false); - controller.getOperandStack().replace(ClassHelper.Iterator_TYPE); - } - - writeForInLoopControlAndBlock(loop); - } - - private void writeEnumerationBasedForEachLoop( - ForStatement loop, - Expression collectionExpression, - ClassNode collectionType) { - + private void writeEnumerationBasedForEachLoop(final ForStatement loop, final Expression collectionExpression, final ClassNode collectionType) { CompileStack compileStack = controller.getCompileStack(); - MethodVisitor mv = controller.getMethodVisitor(); OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); - // Declare the loop counter. BytecodeVariable variable = compileStack.defineVariable(loop.getVariable(), false); + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); collectionExpression.visit(controller.getAcg()); - // Then get the iterator and generate the loop control - int enumIdx = compileStack.defineTemporaryVariable("$enum", ENUMERATION_CLASSNODE, true); - Label continueLabel = compileStack.getContinueLabel(); - Label breakLabel = compileStack.getBreakLabel(); - mv.visitLabel(continueLabel); mv.visitVarInsn(ALOAD, enumIdx); ENUMERATION_HASMORE_METHOD.call(mv); @@ -253,12 +209,30 @@ public class StaticTypesStatementWriter extends StatementWriter { operandStack.push(ClassHelper.OBJECT_TYPE); operandStack.storeVar(variable); - // Generate the loop body loop.getLoopBlock().visit(controller.getAcg()); mv.visitJumpInsn(GOTO, continueLabel); mv.visitLabel(breakLabel); - } + private void writeIteratorBasedForEachLoop(final ForStatement loop, final Expression collectionExpression, final ClassNode collectionType) { + // GROOVY-10476: BaseStream provides an iterator() but does not implement Iterable + MethodNode iterator = collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY); + if (iterator == null) { + iterator = GeneralUtils.getInterfacesAndSuperInterfaces(collectionType).stream() + .map(in -> in.getMethod("iterator", Parameter.EMPTY_ARRAY)) + .filter(Objects::nonNull).findFirst().orElse(null); + } + if (iterator != null && iterator.getReturnType().equals(ClassHelper.Iterator_TYPE)) { + MethodCallExpression call = GeneralUtils.callX(collectionExpression, "iterator"); + call.setImplicitThis(false); + call.setMethodTarget(iterator); + call.visit(controller.getAcg()); + } else { + collectionExpression.visit(controller.getAcg()); + controller.getMethodVisitor().visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "iterator", "(Ljava/lang/Object;)Ljava/util/Iterator;", false); + controller.getOperandStack().replace(ClassHelper.Iterator_TYPE); + } + writeForInLoopControlAndBlock(loop); + } } diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index b121c4b..3dabf27 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -163,6 +163,7 @@ import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; import static org.codehaus.groovy.ast.ClassHelper.PATTERN_TYPE; import static org.codehaus.groovy.ast.ClassHelper.RANGE_TYPE; import static org.codehaus.groovy.ast.ClassHelper.SET_TYPE; +import static org.codehaus.groovy.ast.ClassHelper.STREAM_TYPE; import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE; import static org.codehaus.groovy.ast.ClassHelper.Short_TYPE; import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; @@ -1985,32 +1986,34 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { } /** - * Given a loop collection type, returns the inferred type of the loop element. Used, for - * example, to infer the element type of a (for e in list) loop. + * Returns the inferred loop element type given a loop collection type. Used, + * for example, to infer the element type of a {@code for (e in list)} loop. * * @param collectionType the type of the collection * @return the inferred component type + * @see #inferComponentType */ public static ClassNode inferLoopElementType(final ClassNode collectionType) { ClassNode componentType = collectionType.getComponentType(); if (componentType == null) { - if (implementsInterfaceOrIsSubclassOf(collectionType, ITERABLE_TYPE)) { - ClassNode intf = GenericsUtils.parameterizeType(collectionType, ITERABLE_TYPE); - GenericsType[] genericsTypes = intf.getGenericsTypes(); - componentType = genericsTypes[0].getType(); - } else if (implementsInterfaceOrIsSubclassOf(collectionType, MAP_TYPE)) { - // GROOVY-6240 - ClassNode intf = GenericsUtils.parameterizeType(collectionType, MAP_TYPE); - GenericsType[] genericsTypes = intf.getGenericsTypes(); - componentType = MAP_ENTRY_TYPE.getPlainNodeReference(); - componentType.setGenericsTypes(genericsTypes); + if (isOrImplements(collectionType, ITERABLE_TYPE)) { + ClassNode col = GenericsUtils.parameterizeType(collectionType, ITERABLE_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + + } else if (isOrImplements(collectionType, MAP_TYPE)) { // GROOVY-6240 + ClassNode col = GenericsUtils.parameterizeType(collectionType, MAP_TYPE); + componentType = makeClassSafe0(MAP_ENTRY_TYPE, col.getGenericsTypes()); + + } else if (isOrImplements(collectionType, STREAM_TYPE)) { // GROOVY-10476 + ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + + } else if (isOrImplements(collectionType, ENUMERATION_TYPE)) { // GROOVY-6123 + ClassNode col = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + } else if (isStringType(collectionType)) { componentType = STRING_TYPE; - } else if (ENUMERATION_TYPE.equals(collectionType)) { - // GROOVY-6123 - ClassNode intf = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); - GenericsType[] genericsTypes = intf.getGenericsTypes(); - componentType = genericsTypes[0].getType(); } else { componentType = OBJECT_TYPE; } diff --git a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy index dc0fb19..4d29461 100644 --- a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy +++ b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy @@ -1161,7 +1161,7 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-8133 - void testSpreadDotOperator() { + void testSpreadDot() { assertScript ''' def list = ['a','b','c'].stream()*.toUpperCase() assert list == ['A', 'B', 'C'] @@ -1186,6 +1186,17 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-10476 + void testForInLoop() { + assertScript ''' + def list = [] + for (item in ['a','b','c'].stream()) { + list.add(item.toUpperCase()) + } + assert list == ['A', 'B', 'C'] + ''' + } + void testBoxingShouldCostMore() { assertScript ''' int foo(int x) { 1 }
