diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 3a046992ca..615591076f 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -2380,6 +2380,29 @@ public void testCompileStatic8051() { runConformTest(sources, "1"); } + @Test + public void testCompileStatic8133() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.*\n" + + "import static org.codehaus.groovy.transform.stc.StaticTypesMarker.*\n" + + "\n" + + "@CompileStatic void test() {\n" + + " @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" + + " def list_type = node.getNodeMetaData(INFERRED_TYPE)\n" + + " assert list_type?.toString(false) == 'java.util.List'\n" + + " })\n" + + " def list = ['foo','bar','baz'].stream()*.toUpperCase()\n" + + " print list\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "[FOO, BAR, BAZ]"); + } + @Test public void testCompileStatic8176() { //@formatter:off @@ -7278,4 +7301,24 @@ public void testCompileStatic10457() { runConformTest(sources, "works"); } + + @Test + public void testCompileStatic10476() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " def list = []\n" + + " for (e in ['foo','bar','baz'].stream()) {\n" + + " list.add(e.toUpperCase())\n" + + " }\n" + + " print list\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "[FOO, BAR, BAZ]"); + } } diff --git a/base/org.codehaus.groovy25/.checkstyle b/base/org.codehaus.groovy25/.checkstyle index 1e919c56ae..c07961fc02 100644 --- a/base/org.codehaus.groovy25/.checkstyle +++ b/base/org.codehaus.groovy25/.checkstyle @@ -53,7 +53,7 @@ - + diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java new file mode 100644 index 0000000000..2b0be1bb92 --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +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.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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.Enumeration; +import java.util.Objects; + +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ARRAYLENGTH; +import static groovyjarjarasm.asm.Opcodes.BALOAD; +import static groovyjarjarasm.asm.Opcodes.CALOAD; +import static groovyjarjarasm.asm.Opcodes.DALOAD; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.FALOAD; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.IALOAD; +import static groovyjarjarasm.asm.Opcodes.ICONST_0; +import static groovyjarjarasm.asm.Opcodes.IFEQ; +import static groovyjarjarasm.asm.Opcodes.IFNULL; +import static groovyjarjarasm.asm.Opcodes.IF_ICMPGE; +import static groovyjarjarasm.asm.Opcodes.ILOAD; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.LALOAD; +import static groovyjarjarasm.asm.Opcodes.SALOAD; + +/** + * A class to write out the optimized statements + */ +public class StaticTypesStatementWriter extends StatementWriter { + + 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"); + + private final StaticTypesWriterController controller; + + public StaticTypesStatementWriter(StaticTypesWriterController controller) { + super(controller); + this.controller = controller; + } + + @Override + public void writeBlockStatement(BlockStatement statement) { + controller.switchToFastPath(); + super.writeBlockStatement(statement); + controller.switchToSlowPath(); + } + + @Override + protected void writeForInLoop(final ForStatement loop) { + 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(); + Expression collectionExpression = loop.getCollectionExpression(); + ClassNode collectionType = typeChooser.resolveType(collectionExpression, controller.getClassNode()); + Parameter loopVariable = loop.getVariable(); + int size = operandStack.getStackLength(); + if (collectionType.isArray() && loopVariable.getType().equals(collectionType.getComponentType())) { // GRECLIPSE edit + writeOptimizedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } else if (ENUMERATION_CLASSNODE.equals(collectionType)) { + writeEnumerationBasedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } else { + writeIteratorBasedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } + operandStack.popDownTo(size); + 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); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + AsmClassGenerator acg = controller.getAcg(); + + // load array on stack + collectionExpression.visit(acg); + mv.visitInsn(DUP); + int array = compileStack.defineTemporaryVariable("$arr", collectionType, true); + mv.visitJumpInsn(IFNULL, breakLabel); + + // $len = array.length + mv.visitVarInsn(ALOAD, array); + mv.visitInsn(ARRAYLENGTH); + operandStack.push(ClassHelper.int_TYPE); + int arrayLen = compileStack.defineTemporaryVariable("$len", ClassHelper.int_TYPE, true); + + // $idx = 0 + mv.visitInsn(ICONST_0); + operandStack.push(ClassHelper.int_TYPE); + int loopIdx = compileStack.defineTemporaryVariable("$idx", ClassHelper.int_TYPE, true); + + mv.visitLabel(continueLabel); + // $idx<$len? + mv.visitVarInsn(ILOAD, loopIdx); + mv.visitVarInsn(ILOAD, arrayLen); + mv.visitJumpInsn(IF_ICMPGE, breakLabel); + + // get array element + loadFromArray(mv, variable, array, loopIdx); + + // $idx++ + mv.visitIincInsn(loopIdx, 1); + + // loop body + loop.getLoopBlock().visit(acg); + + mv.visitJumpInsn(GOTO, continueLabel); + + mv.visitLabel(breakLabel); + + compileStack.removeVar(loopIdx); + compileStack.removeVar(arrayLen); + compileStack.removeVar(array); + } + + private void loadFromArray(MethodVisitor mv, BytecodeVariable variable, int array, int iteratorIdx) { + OperandStack os = controller.getOperandStack(); + mv.visitVarInsn(ALOAD, array); + mv.visitVarInsn(ILOAD, iteratorIdx); + + ClassNode varType = variable.getType(); + boolean primitiveType = ClassHelper.isPrimitiveType(varType); + boolean isByte = ClassHelper.byte_TYPE.equals(varType); + boolean isShort = ClassHelper.short_TYPE.equals(varType); + boolean isInt = ClassHelper.int_TYPE.equals(varType); + boolean isLong = ClassHelper.long_TYPE.equals(varType); + boolean isFloat = ClassHelper.float_TYPE.equals(varType); + boolean isDouble = ClassHelper.double_TYPE.equals(varType); + boolean isChar = ClassHelper.char_TYPE.equals(varType); + boolean isBoolean = ClassHelper.boolean_TYPE.equals(varType); + + if (primitiveType) { + if (isByte) { + mv.visitInsn(BALOAD); + } + if (isShort) { + mv.visitInsn(SALOAD); + } + if (isInt || isChar || isBoolean) { + mv.visitInsn(isChar ? CALOAD : isBoolean ? BALOAD : IALOAD); + } + if (isLong) { + mv.visitInsn(LALOAD); + } + if (isFloat) { + mv.visitInsn(FALOAD); + } + if (isDouble) { + mv.visitInsn(DALOAD); + } + } else { + mv.visitInsn(AALOAD); + } + os.push(varType); + os.storeVar(variable); + } + + private void writeIteratorBasedForEachLoop( + CompileStack compileStack, + OperandStack operandStack, + MethodVisitor mv, + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType, + Parameter loopVariable) { + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + /* GRECLIPSE edit -- GROOVY-10476 + 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()); + */ + MethodNode iterator = collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY); + if (iterator == null) { + iterator = StaticTypeCheckingSupport.collectAllInterfaces(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()); + // GRECLIPSE end + } else { + collectionExpression.visit(controller.getAcg()); + mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "iterator", "(Ljava/lang/Object;)Ljava/util/Iterator;", false); + operandStack.replace(ClassHelper.Iterator_TYPE); + } + + // Then get the iterator and generate the loop control + + int iteratorIdx = compileStack.defineTemporaryVariable("iterator", ClassHelper.Iterator_TYPE, true); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + mv.visitLabel(continueLabel); + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorHasNext(mv); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, iteratorIdx); + writeIteratorNext(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + compileStack.removeVar(iteratorIdx); + } + + private void writeEnumerationBasedForEachLoop( + CompileStack compileStack, + OperandStack operandStack, + MethodVisitor mv, + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType, + Parameter loopVariable) { + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loopVariable, false); + + 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); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_NEXT_METHOD.call(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + } +} diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index c3efcd54e2..40d5d7b4a2 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -139,6 +139,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.IntStream; +import java.util.stream.Stream; import static java.util.stream.Collectors.toMap; import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName; @@ -192,7 +193,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.castX; import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.ast.tools.GenericsUtils.toGenericTypesString; @@ -308,6 +308,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode MAP_ENTRY_TYPE = make(Map.Entry.class); protected static final ClassNode ENUMERATION_TYPE = make(Enumeration.class); // GRECLIPSE add + private static final ClassNode STREAM_TYPE = ClassHelper.make(Stream.class); private static final ClassNode SET_TYPE = ClassHelper.makeWithoutCaching(Set.class); private static final ClassNode LinkedHashSet_TYPE = ClassHelper.makeWithoutCaching(LinkedHashSet.class); // GRECLIPSE end @@ -2463,6 +2464,11 @@ public static ClassNode inferLoopElementType(final ClassNode collectionType) { ClassNode intf = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); GenericsType[] genericsTypes = intf.getGenericsTypes(); componentType = genericsTypes[0].getType(); + // GRECLIPSE add -- GROOVY-10476 + } else if (isOrImplements(collectionType, STREAM_TYPE)) { + ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + // GRECLIPSE end } else { componentType = ClassHelper.OBJECT_TYPE; } @@ -4145,30 +4151,42 @@ public void visitMethodCallExpression(MethodCallExpression call) { inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference()); } // GRECLIPSE end - // if the call expression is a spread operator call, then we must make sure that - // the call is made on a collection type if (call.isSpreadSafe()) { + /* GRECLIPSE edit -- GROOVY-8133 if (!implementsInterfaceOrIsSubclassOf(receiver, Collection_TYPE) && !receiver.isArray()) { addStaticTypeError("Spread operator can only be used on collection types", objectExpression); return; } else { - // type check call as if it was made on component type ClassNode componentType = inferComponentType(receiver, int_TYPE); MethodCallExpression subcall = callX(castX(componentType, EmptyExpression.INSTANCE), name, call.getArguments()); subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); subcall.setImplicitThis(call.isImplicitThis()); visitMethodCallExpression(subcall); - // the inferred type here should be a list of what the subcall returns ClassNode subcallReturnType = getType(subcall); ClassNode listNode = LIST_TYPE.getPlainNodeReference(); listNode.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(subcallReturnType))}); storeType(call, listNode); - // store target method storeTargetMethod(call, (MethodNode) subcall.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET)); typeCheckingContext.popEnclosingMethodCall(); return; } + */ + ClassNode componentType = inferComponentType(receiver, null); + if (componentType == null) { + addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression); + } else { + MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments()); + subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); + subcall.setImplicitThis(call.isImplicitThis()); + visitMethodCallExpression(subcall); + // inferred type should be a list of what sub-call returns + storeType(call, GenericsUtils.makeClassSafe0(LIST_TYPE, getType(subcall).asGenericsType())); + storeTargetMethod(call, subcall.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET)); + } + typeCheckingContext.popEnclosingMethodCall(); + return; + // GRECLIPSE end } Expression callArguments = call.getArguments(); @@ -5535,10 +5553,9 @@ protected static ClassNode getGroupOperationResultType(ClassNode a, ClassNode b) } protected ClassNode inferComponentType(final ClassNode containerType, final ClassNode indexType) { - final ClassNode componentType = containerType.getComponentType(); + ClassNode componentType = containerType.getComponentType(); if (componentType == null) { - // GROOVY-5521 - // try to identify a getAt method + /* GRECLIPSE edit -- GROOVY-8133 typeCheckingContext.pushErrorCollector(); MethodCallExpression vcall = callX(localVarX("_hash_", containerType), "getAt", varX("_index_", indexType)); vcall.setImplicitThis(false); // GROOVY-8943 @@ -5548,9 +5565,35 @@ protected ClassNode inferComponentType(final ClassNode containerType, final Clas typeCheckingContext.popErrorCollector(); } return getType(vcall); - } else { - return componentType; + */ + MethodCallExpression mce; + if (indexType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method + mce = callX(varX("#", containerType), "getAt", varX("selector", indexType)); + } else { // GROOVY-8133: check for an "iterator()" method + mce = callX(varX("#", containerType), "iterator"); + } + mce.setImplicitThis(false); // GROOVY-8943 + + typeCheckingContext.pushErrorCollector(); + try { + visitMethodCallExpression(mce); + } finally { + typeCheckingContext.popErrorCollector(); + } + + if (indexType != null) { + componentType = getType(mce); + } else { + ClassNode iteratorType = getType(mce); + if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null + // ignore the iterator(Object) extension method, since it makes *everything* appear iterable + || !mce.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) { + componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE); + } + } + // GRECLIPSE end } + return componentType; } protected MethodNode findMethodOrFail( diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle index bfe4c5108f..ece82f891a 100644 --- a/base/org.codehaus.groovy30/.checkstyle +++ b/base/org.codehaus.groovy30/.checkstyle @@ -46,7 +46,7 @@ - + diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java new file mode 100644 index 0000000000..3531e3d867 --- /dev/null +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +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.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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.Enumeration; +import java.util.Objects; + +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ARRAYLENGTH; +import static groovyjarjarasm.asm.Opcodes.BALOAD; +import static groovyjarjarasm.asm.Opcodes.CALOAD; +import static groovyjarjarasm.asm.Opcodes.DALOAD; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.FALOAD; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.IALOAD; +import static groovyjarjarasm.asm.Opcodes.ICONST_0; +import static groovyjarjarasm.asm.Opcodes.IFEQ; +import static groovyjarjarasm.asm.Opcodes.IFNULL; +import static groovyjarjarasm.asm.Opcodes.IF_ICMPGE; +import static groovyjarjarasm.asm.Opcodes.ILOAD; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.LALOAD; +import static groovyjarjarasm.asm.Opcodes.SALOAD; + +/** + * A class to write out the optimized statements. + */ +public class StaticTypesStatementWriter extends StatementWriter { + + 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) { + super(controller); + } + + @Override + public void writeBlockStatement(BlockStatement statement) { + controller.switchToFastPath(); + super.writeBlockStatement(statement); + controller.switchToSlowPath(); + } + + @Override + protected void writeForInLoop(final ForStatement loop) { + 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(); + Expression collectionExpression = loop.getCollectionExpression(); + ClassNode collectionType = typeChooser.resolveType(collectionExpression, controller.getClassNode()); + Parameter loopVariable = loop.getVariable(); + int size = operandStack.getStackLength(); + if (collectionType.isArray() && loopVariable.getType().equals(collectionType.getComponentType())) { // GRECLIPSE edit + writeOptimizedForEachLoop(compileStack, operandStack, mv, loop, collectionExpression, collectionType, loopVariable); + } else if (ENUMERATION_CLASSNODE.equals(collectionType)) { + writeEnumerationBasedForEachLoop(loop, collectionExpression, collectionType); + } else { + writeIteratorBasedForEachLoop(loop, collectionExpression, collectionType); + } + operandStack.popDownTo(size); + 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); + + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + AsmClassGenerator acg = controller.getAcg(); + + // load array on stack + collectionExpression.visit(acg); + mv.visitInsn(DUP); + int array = compileStack.defineTemporaryVariable("$arr", collectionType, true); + mv.visitJumpInsn(IFNULL, breakLabel); + + // $len = array.length + mv.visitVarInsn(ALOAD, array); + mv.visitInsn(ARRAYLENGTH); + operandStack.push(ClassHelper.int_TYPE); + int arrayLen = compileStack.defineTemporaryVariable("$len", ClassHelper.int_TYPE, true); + + // $idx = 0 + mv.visitInsn(ICONST_0); + operandStack.push(ClassHelper.int_TYPE); + int loopIdx = compileStack.defineTemporaryVariable("$idx", ClassHelper.int_TYPE, true); + + mv.visitLabel(continueLabel); + // $idx<$len? + mv.visitVarInsn(ILOAD, loopIdx); + mv.visitVarInsn(ILOAD, arrayLen); + mv.visitJumpInsn(IF_ICMPGE, breakLabel); + + // get array element + loadFromArray(mv, variable, array, loopIdx); + + // $idx++ + mv.visitIincInsn(loopIdx, 1); + + // loop body + loop.getLoopBlock().visit(acg); + + mv.visitJumpInsn(GOTO, continueLabel); + + mv.visitLabel(breakLabel); + + compileStack.removeVar(loopIdx); + compileStack.removeVar(arrayLen); + compileStack.removeVar(array); + } + + private void loadFromArray(MethodVisitor mv, BytecodeVariable variable, int array, int iteratorIdx) { + OperandStack os = controller.getOperandStack(); + mv.visitVarInsn(ALOAD, array); + mv.visitVarInsn(ILOAD, iteratorIdx); + + ClassNode varType = variable.getType(); + boolean primitiveType = ClassHelper.isPrimitiveType(varType); + boolean isByte = ClassHelper.byte_TYPE.equals(varType); + boolean isShort = ClassHelper.short_TYPE.equals(varType); + boolean isInt = ClassHelper.int_TYPE.equals(varType); + boolean isLong = ClassHelper.long_TYPE.equals(varType); + boolean isFloat = ClassHelper.float_TYPE.equals(varType); + boolean isDouble = ClassHelper.double_TYPE.equals(varType); + boolean isChar = ClassHelper.char_TYPE.equals(varType); + boolean isBoolean = ClassHelper.boolean_TYPE.equals(varType); + + if (primitiveType) { + if (isByte) { + mv.visitInsn(BALOAD); + } + if (isShort) { + mv.visitInsn(SALOAD); + } + if (isInt || isChar || isBoolean) { + mv.visitInsn(isChar ? CALOAD : isBoolean ? BALOAD : IALOAD); + } + if (isLong) { + mv.visitInsn(LALOAD); + } + if (isFloat) { + mv.visitInsn(FALOAD); + } + if (isDouble) { + mv.visitInsn(DALOAD); + } + } else { + mv.visitInsn(AALOAD); + } + os.push(varType); + os.storeVar(variable); + } + + private void writeIteratorBasedForEachLoop( + ForStatement loop, + Expression collectionExpression, + ClassNode collectionType) { + /* GRECLIPSE edit -- GROOVY-10476 + 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()); + */ + MethodNode iterator = collectionType.getMethod("iterator", Parameter.EMPTY_ARRAY); + if (iterator == null) { + iterator = StaticTypeCheckingSupport.collectAllInterfaces(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()); + // GRECLIPSE end + } 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) { + + CompileStack compileStack = controller.getCompileStack(); + MethodVisitor mv = controller.getMethodVisitor(); + OperandStack operandStack = controller.getOperandStack(); + + // Declare the loop counter. + BytecodeVariable variable = compileStack.defineVariable(loop.getVariable(), false); + + 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); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_NEXT_METHOD.call(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + // Generate the loop body + loop.getLoopBlock().visit(controller.getAcg()); + + mv.visitJumpInsn(GOTO, continueLabel); + mv.visitLabel(breakLabel); + } +} diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 35fbae6932..5e60a18e91 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -138,6 +138,7 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.IntStream; +import java.util.stream.Stream; import static java.util.stream.Collectors.*; import static org.apache.groovy.util.BeanUtils.capitalize; @@ -196,7 +197,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.constX; import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; @@ -333,6 +333,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode MAP_ENTRY_TYPE = ClassHelper.make(Map.Entry.class); protected static final ClassNode ENUMERATION_TYPE = ClassHelper.make(Enumeration.class); // GRECLIPSE add + private static final ClassNode STREAM_TYPE = ClassHelper.make(Stream.class); private static final ClassNode SET_TYPE = ClassHelper.makeWithoutCaching(Set.class); private static final ClassNode LinkedHashSet_TYPE = ClassHelper.makeWithoutCaching(LinkedHashSet.class); // GRECLIPSE end @@ -2298,6 +2299,11 @@ public static ClassNode inferLoopElementType(final ClassNode collectionType) { ClassNode intf = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); GenericsType[] genericsTypes = intf.getGenericsTypes(); componentType = genericsTypes[0].getType(); + // GRECLIPSE add -- GROOVY-10476 + } else if (isOrImplements(collectionType, STREAM_TYPE)) { + ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + // GRECLIPSE end } else { componentType = OBJECT_TYPE; } @@ -3846,30 +3852,42 @@ public void visitMethodCallExpression(final MethodCallExpression call) { inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference()); } // GRECLIPSE end - // if the call expression is a spread operator call, then we must make sure that - // the call is made on a collection type if (call.isSpreadSafe()) { + /* GRECLIPSE edit -- GROOVY-8133 if (!implementsInterfaceOrIsSubclassOf(receiver, Collection_TYPE) && !receiver.isArray()) { addStaticTypeError("Spread operator can only be used on collection types", objectExpression); return; } else { - // type check call as if it was made on component type ClassNode componentType = inferComponentType(receiver, int_TYPE); MethodCallExpression subcall = callX(castX(componentType, EmptyExpression.INSTANCE), name, call.getArguments()); subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); subcall.setImplicitThis(call.isImplicitThis()); visitMethodCallExpression(subcall); - // the inferred type here should be a list of what the subcall returns ClassNode subcallReturnType = getType(subcall); ClassNode listNode = LIST_TYPE.getPlainNodeReference(); listNode.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(subcallReturnType))}); storeType(call, listNode); - // store target method storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); typeCheckingContext.popEnclosingMethodCall(); return; } + */ + ClassNode componentType = inferComponentType(receiver, null); + if (componentType == null) { + addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression); + } else { + MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments()); + subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); + subcall.setImplicitThis(call.isImplicitThis()); + visitMethodCallExpression(subcall); + // inferred type should be a list of what sub-call returns + storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); + storeType(call, GenericsUtils.makeClassSafe0(LIST_TYPE, getType(subcall).asGenericsType())); + } + typeCheckingContext.popEnclosingMethodCall(); + return; + // GRECLIPSE end } Expression callArguments = call.getArguments(); @@ -5272,8 +5290,7 @@ protected static ClassNode getGroupOperationResultType(final ClassNode a, final protected ClassNode inferComponentType(final ClassNode containerType, final ClassNode indexType) { ClassNode componentType = containerType.getComponentType(); if (componentType == null) { - // GROOVY-5521 - // try to identify a getAt method + /* GRECLIPSE edit -- GROOVY-8133 typeCheckingContext.pushErrorCollector(); MethodCallExpression vcall = callX(localVarX("_hash_", containerType), "getAt", varX("_index_", indexType)); vcall.setImplicitThis(false); // GROOVY-8943 @@ -5283,9 +5300,35 @@ protected ClassNode inferComponentType(final ClassNode containerType, final Clas typeCheckingContext.popErrorCollector(); } return getType(vcall); - } else { - return componentType; + */ + MethodCallExpression mce; + if (indexType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method + mce = callX(varX("#", containerType), "getAt", varX("selector", indexType)); + } else { // GROOVY-8133: check for an "iterator()" method + mce = callX(varX("#", containerType), "iterator"); + } + mce.setImplicitThis(false); // GROOVY-8943 + + typeCheckingContext.pushErrorCollector(); + try { + visitMethodCallExpression(mce); + } finally { + typeCheckingContext.popErrorCollector(); + } + + if (indexType != null) { + componentType = getType(mce); + } else { + ClassNode iteratorType = getType(mce); + if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null + // ignore the iterator(Object) extension method, since it makes *everything* appear iterable + || !mce.getNodeMetaData(DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) { + componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE); + } + } + // GRECLIPSE end } + return componentType; } protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) { diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle index 40589c6edc..b1adae287a 100644 --- a/base/org.codehaus.groovy40/.checkstyle +++ b/base/org.codehaus.groovy40/.checkstyle @@ -41,7 +41,7 @@ - + diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java new file mode 100644 index 0000000000..9fddf7e398 --- /dev/null +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +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.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 groovyjarjarasm.asm.Label; +import groovyjarjarasm.asm.MethodVisitor; + +import java.util.Enumeration; +import java.util.Objects; + +import static groovyjarjarasm.asm.Opcodes.AALOAD; +import static groovyjarjarasm.asm.Opcodes.ALOAD; +import static groovyjarjarasm.asm.Opcodes.ARRAYLENGTH; +import static groovyjarjarasm.asm.Opcodes.BALOAD; +import static groovyjarjarasm.asm.Opcodes.CALOAD; +import static groovyjarjarasm.asm.Opcodes.DALOAD; +import static groovyjarjarasm.asm.Opcodes.DUP; +import static groovyjarjarasm.asm.Opcodes.FALOAD; +import static groovyjarjarasm.asm.Opcodes.GOTO; +import static groovyjarjarasm.asm.Opcodes.IALOAD; +import static groovyjarjarasm.asm.Opcodes.ICONST_0; +import static groovyjarjarasm.asm.Opcodes.IFEQ; +import static groovyjarjarasm.asm.Opcodes.IFNULL; +import static groovyjarjarasm.asm.Opcodes.IF_ICMPGE; +import static groovyjarjarasm.asm.Opcodes.ILOAD; +import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC; +import static groovyjarjarasm.asm.Opcodes.LALOAD; +import static groovyjarjarasm.asm.Opcodes.SALOAD; + +/** + * A class to write out the optimized statements. + */ +public class StaticTypesStatementWriter extends StatementWriter { + + 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(final StaticTypesWriterController controller) { + super(controller); + } + + @Override + 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"); + writeStatementLabel(loop); + + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + + compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabels()); + + // identify type of collection + Expression collectionExpression = loop.getCollectionExpression(); + ClassNode collectionType = controller.getTypeChooser().resolveType(collectionExpression, controller.getClassNode()); + + int mark = operandStack.getStackLength(); + Parameter loopVariable = loop.getVariable(); + if (collectionType.isArray() && loopVariable.getType().equals(collectionType.getComponentType())) { + writeOptimizedForEachLoop(loop, loopVariable, collectionExpression, collectionType); + } else if (GeneralUtils.isOrImplements(collectionType, ENUMERATION_CLASSNODE)) { + writeEnumerationBasedForEachLoop(loop, collectionExpression, collectionType); + } else { + writeIteratorBasedForEachLoop(loop, collectionExpression, collectionType); + } + operandStack.popDownTo(mark); + compileStack.pop(); + } + + 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(); + + // load array on stack + collectionExpression.visit(acg); + mv.visitInsn(DUP); + int array = compileStack.defineTemporaryVariable("$arr", collectionType, true); + mv.visitJumpInsn(IFNULL, breakLabel); + + // $len = array.length + mv.visitVarInsn(ALOAD, array); + mv.visitInsn(ARRAYLENGTH); + operandStack.push(ClassHelper.int_TYPE); + int arrayLen = compileStack.defineTemporaryVariable("$len", ClassHelper.int_TYPE, true); + + // $idx = 0 + mv.visitInsn(ICONST_0); + operandStack.push(ClassHelper.int_TYPE); + int loopIdx = compileStack.defineTemporaryVariable("$idx", ClassHelper.int_TYPE, true); + + mv.visitLabel(continueLabel); + // $idx<$len? + mv.visitVarInsn(ILOAD, loopIdx); + mv.visitVarInsn(ILOAD, arrayLen); + mv.visitJumpInsn(IF_ICMPGE, breakLabel); + + // get array element + loadFromArray(mv, operandStack, variable, array, loopIdx); + + // $idx += 1 + mv.visitIincInsn(loopIdx, 1); + + // loop body + loop.getLoopBlock().visit(acg); + + mv.visitJumpInsn(GOTO, continueLabel); + + mv.visitLabel(breakLabel); + + compileStack.removeVar(loopIdx); + compileStack.removeVar(arrayLen); + compileStack.removeVar(array); + } + + 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, index); + ClassNode varType = variable.getType(); + if (ClassHelper.isPrimitiveType(varType)) { + if (ClassHelper.isPrimitiveInt(varType)) { + mv.visitInsn(IALOAD); + } else if (ClassHelper.isPrimitiveLong(varType)) { + mv.visitInsn(LALOAD); + } else if (ClassHelper.isPrimitiveByte(varType) || ClassHelper.isPrimitiveBoolean(varType)) { + mv.visitInsn(BALOAD); + } else if (ClassHelper.isPrimitiveChar(varType)) { + mv.visitInsn(CALOAD); + } else if (ClassHelper.isPrimitiveShort(varType)) { + mv.visitInsn(SALOAD); + } else if (ClassHelper.isPrimitiveFloat(varType)) { + mv.visitInsn(FALOAD); + } else if (ClassHelper.isPrimitiveDouble(varType)) { + mv.visitInsn(DALOAD); + } + } else { + mv.visitInsn(AALOAD); + } + os.push(varType); + os.storeVar(variable); + } + + private void writeEnumerationBasedForEachLoop(final ForStatement loop, final Expression collectionExpression, final ClassNode collectionType) { + CompileStack compileStack = controller.getCompileStack(); + OperandStack operandStack = controller.getOperandStack(); + MethodVisitor mv = controller.getMethodVisitor(); + + BytecodeVariable variable = compileStack.defineVariable(loop.getVariable(), false); + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + + collectionExpression.visit(controller.getAcg()); + + int enumIdx = compileStack.defineTemporaryVariable("$enum", ENUMERATION_CLASSNODE, true); + + mv.visitLabel(continueLabel); + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_HASMORE_METHOD.call(mv); + // note: ifeq tests for ==0, a boolean is 0 if it is false + mv.visitJumpInsn(IFEQ, breakLabel); + + mv.visitVarInsn(ALOAD, enumIdx); + ENUMERATION_NEXT_METHOD.call(mv); + operandStack.push(ClassHelper.OBJECT_TYPE); + operandStack.storeVar(variable); + + 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/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index 315ae94db7..5b9755b53d 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -138,6 +138,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import static java.util.stream.Collectors.toMap; import static org.apache.groovy.util.BeanUtils.capitalize; @@ -217,7 +218,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName; import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements; -import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX; import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; @@ -353,6 +353,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport { protected static final ClassNode ENUMERATION_TYPE = ClassHelper.make(Enumeration.class); protected static final ClassNode MAP_ENTRY_TYPE = ClassHelper.make(Map.Entry.class); protected static final ClassNode ITERABLE_TYPE = ClassHelper.ITERABLE_TYPE; + private static final ClassNode STREAM_TYPE = ClassHelper.make(Stream.class); private static List TUPLE_TYPES = Arrays.stream(ClassHelper.TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(Collectors.toList()); @@ -2044,6 +2045,11 @@ public static ClassNode inferLoopElementType(final ClassNode collectionType) { ClassNode intf = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); GenericsType[] genericsTypes = intf.getGenericsTypes(); componentType = genericsTypes[0].getType(); + // GRECLIPSE add -- GROOVY-10476 + } else if (isOrImplements(collectionType, STREAM_TYPE)) { + ClassNode col = GenericsUtils.parameterizeType(collectionType, STREAM_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + // GRECLIPSE end } else { componentType = OBJECT_TYPE; } @@ -3336,16 +3342,16 @@ public void visitMethodCallExpression(final MethodCallExpression call) { if (objectExpression instanceof ConstructorCallExpression) { // GROOVY-10228 inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference()); } - if (call.isSpreadSafe()) { // make sure receiver is array or collection then check element type - if (!receiver.isArray() && !implementsInterfaceOrIsSubclassOf(receiver, Collection_TYPE)) { - addStaticTypeError("Spread operator can only be used on collection types", objectExpression); + if (call.isSpreadSafe()) { + ClassNode componentType = inferComponentType(receiver, null); + if (componentType == null) { + addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression); } else { - ClassNode componentType = inferComponentType(receiver, int_TYPE); - MethodCallExpression subcall = callX(castX(componentType, EmptyExpression.INSTANCE), name, call.getArguments()); + MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments()); subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber()); subcall.setImplicitThis(call.isImplicitThis()); visitMethodCallExpression(subcall); - // the inferred type here should be a list of what the subcall returns + // inferred type should be a list of what sub-call returns storeType(call, extension.buildListType(getType(subcall))); storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET)); } @@ -4583,23 +4589,36 @@ protected static ClassNode getGroupOperationResultType(final ClassNode a, final return Number_TYPE; } - protected ClassNode inferComponentType(final ClassNode containerType, final ClassNode indexType) { - ClassNode componentType = containerType.getComponentType(); + protected ClassNode inferComponentType(final ClassNode receiverType, final ClassNode subscriptType) { + ClassNode componentType = receiverType.getComponentType(); if (componentType == null) { - // GROOVY-5521 - // try to identify a getAt method + MethodCallExpression mce; + if (subscriptType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method + mce = callX(varX("#", receiverType), "getAt", varX("selector", subscriptType)); + } else { // GROOVY-8133: check for an "iterator()" method + mce = callX(varX("#", receiverType), "iterator"); + } + mce.setImplicitThis(false); // GROOVY-8943 + typeCheckingContext.pushErrorCollector(); - MethodCallExpression vcall = callX(localVarX("_hash_", containerType), "getAt", varX("_index_", indexType)); - vcall.setImplicitThis(false); // GROOVY-8943 try { - visitMethodCallExpression(vcall); + visitMethodCallExpression(mce); } finally { typeCheckingContext.popErrorCollector(); } - return getType(vcall); - } else { - return componentType; + + if (subscriptType != null) { + componentType = getType(mce); + } else { + ClassNode iteratorType = getType(mce); + if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null + // ignore the iterator(Object) extension method, since it makes *everything* appear iterable + || !mce.getNodeMetaData(DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) { + componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE); + } + } } + return componentType; } protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) {