blob: e140c0ff2583d22247830a785d7f4d6051250a7e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.codegen.CodeStream;
import org.eclipse.jdt.internal.compiler.codegen.Label;
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.flow.LoopingFlowContext;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
public class ForeachStatement extends Statement {
public LocalDeclaration elementVariable;
public int elementVariableImplicitWidening = -1;
public Expression collection;
public Statement action;
// set the kind of foreach
private int kind;
// possible kinds of iterating behavior
private static final int ARRAY = 0;
private static final int RAW_ITERABLE = 1;
private static final int GENERIC_ITERABLE = 2;
private TypeBinding collectionElementType;
// loop labels
private Label breakLabel;
private Label continueLabel;
public BlockScope scope;
// secret variables for codegen
public LocalVariableBinding indexVariable;
public LocalVariableBinding collectionVariable; // to store the collection expression value
public LocalVariableBinding maxVariable;
// secret variable names
private static final char[] SecretIndexVariableName = " index".toCharArray(); //$NON-NLS-1$
private static final char[] SecretCollectionVariableName = " collection".toCharArray(); //$NON-NLS-1$
private static final char[] SecretMaxVariableName = " max".toCharArray(); //$NON-NLS-1$
int postCollectionInitStateIndex = -1;
int mergedInitStateIndex = -1;
public ForeachStatement(
LocalDeclaration elementVariable,
Expression collection,
int start) {
this.elementVariable = elementVariable;
this.collection = collection;
this.sourceStart = start;
this.kind = -1;
}
public FlowInfo analyseCode(
BlockScope currentScope,
FlowContext flowContext,
FlowInfo flowInfo) {
// initialize break and continue labels
breakLabel = new Label();
continueLabel = new Label();
// process the element variable and collection
flowInfo = this.elementVariable.analyseCode(scope, flowContext, flowInfo);
FlowInfo condInfo = flowInfo.copy().unconditionalInits().discardNullRelatedInitializations();
condInfo = this.collection.analyseCode(scope, flowContext, condInfo);
// element variable will be assigned when iterating
condInfo.markAsDefinitelyAssigned(this.elementVariable.binding);
this.postCollectionInitStateIndex = currentScope.methodScope().recordInitializationStates(condInfo);
// process the action
LoopingFlowContext loopingContext = new LoopingFlowContext(flowContext, this, breakLabel, continueLabel, scope);
FlowInfo actionInfo = condInfo.initsWhenTrue().copy();
FlowInfo exitBranch;
if (!(action == null || (action.isEmptyBlock()
&& currentScope.compilerOptions().complianceLevel <= ClassFileConstants.JDK1_3))) {
if (!this.action.complainIfUnreachable(actionInfo, scope, false)) {
actionInfo = action.analyseCode(scope, loopingContext, actionInfo);
}
// code generation can be optimized when no need to continue in the loop
exitBranch = condInfo.initsWhenFalse();
exitBranch.addInitializationsFrom(flowInfo); // recover null inits from before condition analysis
if (!actionInfo.isReachable() && !loopingContext.initsOnContinue.isReachable()) {
continueLabel = null;
} else {
actionInfo = actionInfo.mergedWith(loopingContext.initsOnContinue.unconditionalInits());
loopingContext.complainOnDeferredChecks(scope, actionInfo);
exitBranch.addPotentialInitializationsFrom(actionInfo.unconditionalInits());
}
} else {
exitBranch = condInfo.initsWhenFalse();
}
// we need the variable to iterate the collection even if the
// element variable is not used
if (!(this.action == null
|| this.action.isEmptyBlock()
|| ((this.action.bits & IsUsefulEmptyStatementMASK) != 0))) {
switch(this.kind) {
case ARRAY :
this.collectionVariable.useFlag = LocalVariableBinding.USED;
this.indexVariable.useFlag = LocalVariableBinding.USED;
this.maxVariable.useFlag = LocalVariableBinding.USED;
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
this.indexVariable.useFlag = LocalVariableBinding.USED;
break;
}
}
//end of loop
FlowInfo mergedInfo = FlowInfo.mergedOptimizedBranches(
loopingContext.initsOnBreak,
false,
exitBranch,
false,
true /*for(;;){}while(true); unreachable(); */);
mergedInitStateIndex = currentScope.methodScope().recordInitializationStates(mergedInfo);
return mergedInfo;
}
/**
* For statement code generation
*
* @param currentScope org.eclipse.jdt.internal.compiler.lookup.BlockScope
* @param codeStream org.eclipse.jdt.internal.compiler.codegen.CodeStream
*/
public void generateCode(BlockScope currentScope, CodeStream codeStream) {
if ((bits & IsReachableMASK) == 0) {
return;
}
int pc = codeStream.position;
if (this.action == null
|| this.action.isEmptyBlock()
|| ((this.action.bits & IsUsefulEmptyStatementMASK) != 0)) {
codeStream.exitUserScope(scope);
if (mergedInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
return;
}
// generate the initializations
switch(this.kind) {
case ARRAY :
collection.generateCode(scope, codeStream, true);
codeStream.store(this.collectionVariable, false);
codeStream.iconst_0();
codeStream.store(this.indexVariable, false);
codeStream.load(this.collectionVariable);
codeStream.arraylength();
codeStream.store(this.maxVariable, false);
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
collection.generateCode(scope, codeStream, true);
// declaringClass.iterator();
final TypeBinding collectionTypeBinding = collection.resolvedType.erasure();
MethodBinding iteratorMethodBinding =
new MethodBinding(
AccPublic,
"iterator".toCharArray(),//$NON-NLS-1$
scope.getJavaUtilIterator(),
TypeConstants.NoParameters,
TypeConstants.NoExceptions,
(ReferenceBinding) collectionTypeBinding);
if (collectionTypeBinding.isInterface()) {
codeStream.invokeinterface(iteratorMethodBinding);
} else {
codeStream.invokevirtual(iteratorMethodBinding);
}
codeStream.store(this.indexVariable, false);
break;
}
// label management
Label actionLabel = new Label(codeStream);
Label conditionLabel = new Label(codeStream);
breakLabel.initialize(codeStream);
if (this.continueLabel != null) {
this.continueLabel.initialize(codeStream);
}
// jump over the actionBlock
codeStream.goto_(conditionLabel);
// generate the loop action
actionLabel.place();
// generate the loop action
if (this.elementVariable.binding.resolvedPosition != -1) {
switch(this.kind) {
case ARRAY :
codeStream.load(this.collectionVariable);
codeStream.load(this.indexVariable);
codeStream.arrayAt(this.collectionElementType.id);
if (this.elementVariableImplicitWidening != -1) {
codeStream.generateImplicitConversion(this.elementVariableImplicitWidening);
}
codeStream.store(this.elementVariable.binding, false);
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
codeStream.load(this.indexVariable);
codeStream.invokeJavaUtilIteratorNext();
if (this.elementVariable.binding.type.id != T_JavaLangObject) {
if (this.elementVariableImplicitWidening != -1) {
codeStream.checkcast(this.collectionElementType);
codeStream.generateImplicitConversion(this.elementVariableImplicitWidening);
} else {
codeStream.checkcast(this.elementVariable.binding.type);
}
}
codeStream.store(this.elementVariable.binding, false);
break;
}
codeStream.addVisibleLocalVariable(this.elementVariable.binding);
if (this.postCollectionInitStateIndex != -1) {
codeStream.addDefinitelyAssignedVariables(
currentScope,
this.postCollectionInitStateIndex);
}
} else {
// if unused variable, some side effects still need to be performed (86487)
switch(this.kind) {
case ARRAY :
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
// still advance in iterator to prevent infinite loop
codeStream.load(this.indexVariable);
codeStream.invokeJavaUtilIteratorNext();
codeStream.pop();
break;
}
}
this.action.generateCode(scope, codeStream);
// continuation point
int continuationPC = codeStream.position;
if (this.continueLabel != null) {
this.continueLabel.place();
// generate the increments for next iteration
switch(this.kind) {
case ARRAY :
codeStream.iinc(this.indexVariable.resolvedPosition, 1);
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
break;
}
}
// generate the condition
conditionLabel.place();
if (this.postCollectionInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, postCollectionInitStateIndex);
}
switch(this.kind) {
case ARRAY :
codeStream.load(this.indexVariable);
codeStream.load(this.maxVariable);
codeStream.if_icmplt(actionLabel);
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
codeStream.load(this.indexVariable);
codeStream.invokeJavaUtilIteratorHasNext();
codeStream.ifne(actionLabel);
break;
}
codeStream.recordPositionsFrom(continuationPC, this.elementVariable.sourceStart);
breakLabel.place();
codeStream.exitUserScope(scope);
if (mergedInitStateIndex != -1) {
codeStream.removeNotDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
codeStream.addDefinitelyAssignedVariables(currentScope, mergedInitStateIndex);
}
codeStream.recordPositionsFrom(pc, this.sourceStart);
}
public StringBuffer printStatement(int tab, StringBuffer output) {
printIndent(tab, output).append("for ("); //$NON-NLS-1$
this.elementVariable.print(0, output);
output.append(" : ");//$NON-NLS-1$
this.collection.print(0, output).append(") "); //$NON-NLS-1$
//block
if (this.action == null) {
output.append(';');
} else {
output.append('\n');
this.action.printStatement(tab + 1, output); //$NON-NLS-1$
}
return output;
}
public void resolve(BlockScope upperScope) {
// use the scope that will hold the init declarations
scope = new BlockScope(upperScope);
this.elementVariable.resolve(scope); // collection expression can see itemVariable
TypeBinding elementType = this.elementVariable.type.resolvedType;
TypeBinding collectionType = this.collection.resolveType(scope);
this.collection.computeConversion(scope, collectionType, collectionType);
boolean hasError = elementType == null || collectionType == null;
if (!hasError) {
if (collectionType.isArrayType()) { // for(E e : E[])
this.kind = ARRAY;
this.collectionElementType = ((ArrayBinding) collectionType).elementsType();
if (!collectionElementType.isCompatibleWith(elementType)
&& !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
}
// in case we need to do a conversion
int compileTimeTypeID = collectionElementType.id;
if (elementType.isBaseType()) {
if (!collectionElementType.isBaseType()) {
compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = UNBOXING;
if (elementType.isBaseType()) {
this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
scope.problemReporter().autoboxing(collection, collectionElementType, elementType);
}
} else {
this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
}
} else {
if (collectionElementType.isBaseType()) {
int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
compileTimeTypeID = boxedID;
scope.problemReporter().autoboxing(collection, collectionElementType, elementType);
}
}
} else if (collectionType instanceof ReferenceBinding) {
ReferenceBinding iterableType = ((ReferenceBinding)collectionType).findSuperTypeErasingTo(T_JavaLangIterable, false /*Iterable is not a class*/);
if (iterableType != null) {
if (iterableType.isParameterizedType()) { // for(E e : Iterable<E>)
ParameterizedTypeBinding parameterizedType = (ParameterizedTypeBinding)iterableType;
if (parameterizedType.arguments.length == 1) { // per construction can only be one
this.kind = GENERIC_ITERABLE;
this.collectionElementType = parameterizedType.arguments[0];
if (!collectionElementType.isCompatibleWith(elementType)
&& !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
}
int compileTimeTypeID = collectionElementType.id;
// no conversion needed as only for reference types
if (elementType.isBaseType()) {
if (!collectionElementType.isBaseType()) {
compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = UNBOXING;
if (elementType.isBaseType()) {
this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
}
} else {
this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
}
} else {
if (collectionElementType.isBaseType()) {
int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
compileTimeTypeID = boxedID;
}
}
}
} else if (iterableType.isGenericType()) { // for (T t : Iterable<T>) - in case used inside Iterable itself
if (iterableType.typeVariables().length == 1) {
this.kind = GENERIC_ITERABLE;
this.collectionElementType = iterableType.typeVariables()[0];
if (!collectionElementType.isCompatibleWith(elementType)
&& !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
}
int compileTimeTypeID = collectionElementType.id;
// no conversion needed as only for reference types
if (elementType.isBaseType()) {
if (!collectionElementType.isBaseType()) {
compileTimeTypeID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = UNBOXING;
if (elementType.isBaseType()) {
this.elementVariableImplicitWidening |= (elementType.id << 4) + compileTimeTypeID;
}
} else {
this.elementVariableImplicitWidening = (elementType.id << 4) + compileTimeTypeID;
}
} else {
if (collectionElementType.isBaseType()) {
int boxedID = scope.environment().computeBoxingType(collectionElementType).id;
this.elementVariableImplicitWidening = BOXING | (compileTimeTypeID << 4) | compileTimeTypeID; // use primitive type in implicit conversion
compileTimeTypeID = boxedID;
}
}
}
} else if (iterableType.isRawType()) { // for(Object o : Iterable)
this.kind = RAW_ITERABLE;
this.collectionElementType = scope.getJavaLangObject();
if (!collectionElementType.isCompatibleWith(elementType)
&& !scope.isBoxingCompatibleWith(collectionElementType, elementType)) {
scope.problemReporter().notCompatibleTypesErrorInForeach(collection, collectionElementType, elementType);
}
// no conversion needed as only for reference types
}
}
}
switch(this.kind) {
case ARRAY :
// allocate #index secret variable (of type int)
this.indexVariable = new LocalVariableBinding(SecretIndexVariableName, IntBinding, AccDefault, false);
scope.addLocalVariable(this.indexVariable);
this.indexVariable.setConstant(NotAConstant); // not inlinable
// allocate #max secret variable
this.maxVariable = new LocalVariableBinding(SecretMaxVariableName, IntBinding, AccDefault, false);
scope.addLocalVariable(this.maxVariable);
this.maxVariable.setConstant(NotAConstant); // not inlinable
// add #array secret variable (of collection type)
this.collectionVariable = new LocalVariableBinding(SecretCollectionVariableName, collectionType, AccDefault, false);
scope.addLocalVariable(this.collectionVariable);
this.collectionVariable.setConstant(NotAConstant); // not inlinable
break;
case RAW_ITERABLE :
case GENERIC_ITERABLE :
// allocate #index secret variable (of type Iterator)
this.indexVariable = new LocalVariableBinding(SecretIndexVariableName, scope.getJavaUtilIterator(), AccDefault, false);
scope.addLocalVariable(this.indexVariable);
this.indexVariable.setConstant(NotAConstant); // not inlinable
break;
default :
scope.problemReporter().invalidTypeForCollection(collection);
}
}
if (action != null) {
action.resolve(scope);
}
}
public void traverse(
ASTVisitor visitor,
BlockScope blockScope) {
if (visitor.visit(this, blockScope)) {
this.elementVariable.traverse(visitor, scope);
this.collection.traverse(visitor, scope);
if (action != null) {
action.traverse(visitor, scope);
}
}
visitor.endVisit(this, blockScope);
}
}