blob: dc808a9b38fb3473900f935286b989fceb1d2fa7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2008 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.wst.jsdt.internal.compiler.ast;
import org.eclipse.wst.jsdt.core.JavaScriptCore;
import org.eclipse.wst.jsdt.core.ast.IASTNode;
import org.eclipse.wst.jsdt.core.ast.ISwitchStatement;
import org.eclipse.wst.jsdt.internal.compiler.ASTVisitor;
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowContext;
import org.eclipse.wst.jsdt.internal.compiler.flow.FlowInfo;
import org.eclipse.wst.jsdt.internal.compiler.flow.SwitchFlowContext;
import org.eclipse.wst.jsdt.internal.compiler.impl.Constant;
import org.eclipse.wst.jsdt.internal.compiler.lookup.BlockScope;
import org.eclipse.wst.jsdt.internal.compiler.lookup.SourceTypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.SyntheticMethodBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.wst.jsdt.internal.compiler.lookup.TypeIds;
public class SwitchStatement extends Statement implements ISwitchStatement{
public Expression expression;
public Statement[] statements;
public BlockScope scope;
public int explicitDeclarations;
public CaseStatement[] cases;
public CaseStatement defaultCase;
public int blockStart;
public int caseCount;
Constant[] constants;
// fallthrough
public final static int CASE = 0;
public final static int FALLTHROUGH = 1;
public final static int ESCAPING = 2;
public SyntheticMethodBinding synthetic; // use for switch on enums types
// for local variables table attributes
int preSwitchInitStateIndex = -1;
int mergedInitStateIndex = -1;
public FlowInfo analyseCode(
BlockScope currentScope,
FlowContext flowContext,
FlowInfo flowInfo) {
try {
flowInfo = expression.analyseCode(currentScope, flowContext, flowInfo);
SwitchFlowContext switchContext =
new SwitchFlowContext(flowContext, this);
// analyse the block by considering specially the case/default statements (need to bind them
// to the entry point)
FlowInfo caseInits = FlowInfo.DEAD_END;
// in case of statements before the first case
// preSwitchInitStateIndex =
// currentScope.methodScope().recordInitializationStates(flowInfo);
int caseIndex = 0;
if (statements != null) {
boolean didAlreadyComplain = false;
int fallThroughState = CASE;
for (int i = 0, max = statements.length; i < max; i++) {
Statement statement = statements[i];
if ((caseIndex < caseCount) && (statement == cases[caseIndex])) { // statement is a case
this.scope.enclosingCase = cases[caseIndex]; // record entering in a switch case block
caseIndex++;
if (fallThroughState == FALLTHROUGH
&& (statement.bits & ASTNode.DocumentedFallthrough) == 0) { // the case is not fall-through protected by a line comment
scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase);
}
caseInits = caseInits.mergedWith(flowInfo.unconditionalInits());
didAlreadyComplain = false; // reset complaint
fallThroughState = CASE;
} else if (statement == defaultCase) { // statement is the default case
this.scope.enclosingCase = defaultCase; // record entering in a switch case block
if (fallThroughState == FALLTHROUGH
&& (statement.bits & ASTNode.DocumentedFallthrough) == 0) {
scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase);
}
caseInits = caseInits.mergedWith(flowInfo.unconditionalInits());
didAlreadyComplain = false; // reset complaint
fallThroughState = CASE;
} else {
fallThroughState = FALLTHROUGH; // reset below if needed
}
if (!statement.complainIfUnreachable(caseInits, scope, didAlreadyComplain)) {
caseInits = statement.analyseCode(scope, switchContext, caseInits);
if (caseInits == FlowInfo.DEAD_END) {
fallThroughState = ESCAPING;
}
} else {
didAlreadyComplain = true;
}
}
}
final TypeBinding resolvedTypeBinding = this.expression.resolvedType;
if (caseCount > 0 && resolvedTypeBinding.isEnum()) {
final SourceTypeBinding sourceTypeBinding = this.scope.classScope().referenceContext.binding;
this.synthetic = sourceTypeBinding.addSyntheticMethodForSwitchEnum(resolvedTypeBinding);
}
// if no default case, then record it may jump over the block directly to the end
if (defaultCase == null) {
// only retain the potential initializations
flowInfo.addPotentialInitializationsFrom(
caseInits.mergedWith(switchContext.initsOnBreak));
// mergedInitStateIndex =
// currentScope.methodScope().recordInitializationStates(flowInfo);
return flowInfo;
}
// merge all branches inits
FlowInfo mergedInfo = caseInits.mergedWith(switchContext.initsOnBreak);
// mergedInitStateIndex =
// currentScope.methodScope().recordInitializationStates(mergedInfo);
return mergedInfo;
} finally {
if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block
}
}
public StringBuffer printStatement(int indent, StringBuffer output) {
printIndent(indent, output).append("switch ("); //$NON-NLS-1$
expression.printExpression(0, output).append(") {"); //$NON-NLS-1$
if (statements != null) {
for (int i = 0; i < statements.length; i++) {
output.append('\n');
if (statements[i] instanceof CaseStatement) {
statements[i].printStatement(indent, output);
} else {
statements[i].printStatement(indent+2, output);
}
}
}
output.append("\n"); //$NON-NLS-1$
return printIndent(indent, output).append('}');
}
public void resolve(BlockScope upperScope) {
try {
// boolean isEnumSwitch = false;
TypeBinding expressionType = expression.resolveType(upperScope);
if (expressionType != null) {
expression.computeConversion(upperScope, expressionType, expressionType);
switch (expressionType.id)
{
case TypeIds.T_any:
case TypeIds.T_undefined:
case TypeIds.T_char:
case TypeIds.T_JavaLangString:
case TypeIds.T_int:
break;
default:
upperScope.problemReporter().incorrectSwitchType(expression, expressionType);
expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon
}
}
if (statements != null) {
scope = !JavaScriptCore.IS_ECMASCRIPT4 ? upperScope : new BlockScope(upperScope);
int length;
// collection of cases is too big but we will only iterate until caseCount
cases = new CaseStatement[length = statements.length];
this.constants = new Constant[length];
CaseStatement[] duplicateCaseStatements = null;
int duplicateCaseStatementsCounter = 0;
int counter = 0;
for (int i = 0; i < length; i++) {
Constant constant;
final Statement statement = statements[i];
if ((constant = statement.resolveCase(scope, expressionType, this)) != Constant.NotAConstant) {
Constant key = constant;
if (constant==null)
continue;
//----check for duplicate case statement------------
for (int j = 0; j < counter; j++) {
if (this.constants[j].equals(key)) {
final CaseStatement currentCaseStatement = (CaseStatement) statement;
if (duplicateCaseStatements == null) {
scope.problemReporter().duplicateCase(cases[j]);
scope.problemReporter().duplicateCase(currentCaseStatement);
duplicateCaseStatements = new CaseStatement[length];
duplicateCaseStatements[duplicateCaseStatementsCounter++] = cases[j];
duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
} else {
boolean found = false;
searchReportedDuplicate: for (int k = 2; k < duplicateCaseStatementsCounter; k++) {
if (duplicateCaseStatements[k] == statement) {
found = true;
break searchReportedDuplicate;
}
}
if (!found) {
scope.problemReporter().duplicateCase(currentCaseStatement);
duplicateCaseStatements[duplicateCaseStatementsCounter++] = currentCaseStatement;
}
}
}
}
this.constants[counter++] = key;
}
}
if (length != counter) { // resize constants array
System.arraycopy(this.constants, 0, this.constants = new Constant[counter], 0, counter);
}
} else {
if ((this.bits & UndocumentedEmptyBlock) != 0) {
upperScope.problemReporter().undocumentedEmptyBlock(this.blockStart, this.sourceEnd);
}
}
// for enum switch, check if all constants are accounted for (if no default)
// if (isEnumSwitch && defaultCase == null
// && upperScope.compilerOptions().getSeverity(CompilerOptions.IncompleteEnumSwitch) != ProblemSeverities.Ignore) {
// int constantCount = this.constants == null ? 0 : this.constants.length; // could be null if no case statement
// if (constantCount == caseCount // ignore diagnosis if unresolved constants
// && caseCount != ((ReferenceBinding)expressionType).enumConstantCount()) {
// FieldBinding[] enumFields = ((ReferenceBinding)expressionType.erasure()).fields();
// for (int i = 0, max = enumFields.length; i <max; i++) {
// FieldBinding enumConstant = enumFields[i];
// if ((enumConstant.modifiers & ClassFileConstants.AccEnum) == 0) continue;
// findConstant : {
// for (int j = 0; j < caseCount; j++) {
// if ((enumConstant.id + 1) == this.constants[j]) // zero should not be returned see bug 141810
// break findConstant;
// }
// // enum constant did not get referenced from switch
// upperScope.problemReporter().missingEnumConstantCase(this, enumConstant);
// }
// }
// }
// }
} finally {
if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block
}
}
public void traverse(
ASTVisitor visitor,
BlockScope blockScope) {
if (visitor.visit(this, blockScope)) {
expression.traverse(visitor, scope);
if (statements != null) {
int statementsLength = statements.length;
for (int i = 0; i < statementsLength; i++)
statements[i].traverse(visitor, scope);
}
}
visitor.endVisit(this, blockScope);
}
public int getASTType() {
return IASTNode.SWITCH_STATEMENT;
}
}