blob: e53439ff66fb080ac872a8465d9cf85fbff96496 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018, 2020 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
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.ASSIGNMENT_CONTEXT;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.INVOCATION_CONTEXT;
import static org.eclipse.jdt.internal.compiler.ast.ExpressionContext.VANILLA_CONTEXT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.compiler.CharOperation;
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.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.PolyTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
public class SwitchExpression extends SwitchStatement implements IPolyExpression {
/* package */ TypeBinding expectedType;
private ExpressionContext expressionContext = VANILLA_CONTEXT;
private boolean isPolyExpression = false;
private TypeBinding[] originalValueResultExpressionTypes;
private TypeBinding[] finalValueResultExpressionTypes;
private int nullStatus = FlowInfo.UNKNOWN;
public List<Expression> resultExpressions;
public boolean resolveAll;
/* package */ List<Integer> resultExpressionNullStatus;
LocalVariableBinding hiddenYield;
/* package */ int hiddenYieldResolvedPosition = -1;
public boolean containsTry = false;
private static Map<TypeBinding, TypeBinding[]> type_map;
static final char[] SECRET_YIELD_VALUE_NAME = " yieldValue".toCharArray(); //$NON-NLS-1$
int yieldResolvedPosition = -1;
List<LocalVariableBinding> typesOnStack;
static {
type_map = new HashMap<TypeBinding, TypeBinding[]>();
type_map.put(TypeBinding.CHAR, new TypeBinding[] {TypeBinding.CHAR, TypeBinding.INT});
type_map.put(TypeBinding.SHORT, new TypeBinding[] {TypeBinding.SHORT, TypeBinding.BYTE, TypeBinding.INT});
type_map.put(TypeBinding.BYTE, new TypeBinding[] {TypeBinding.BYTE, TypeBinding.INT});
}
@Override
public void setExpressionContext(ExpressionContext context) {
this.expressionContext = context;
}
@Override
public void setExpectedType(TypeBinding expectedType) {
this.expectedType = expectedType;
}
@Override
public ExpressionContext getExpressionContext() {
return this.expressionContext;
}
@Override
protected boolean ignoreMissingDefaultCase(CompilerOptions compilerOptions, boolean isEnumSwitch) {
return isEnumSwitch; // mandatory error if not enum in switch expressions
}
@Override
protected void reportMissingEnumConstantCase(BlockScope upperScope, FieldBinding enumConstant) {
upperScope.problemReporter().missingEnumConstantCase(this, enumConstant);
}
@Override
protected int getFallThroughState(Statement stmt, BlockScope blockScope) {
if ((stmt instanceof Expression && ((Expression) stmt).isTrulyExpression())|| stmt instanceof ThrowStatement)
return BREAKING;
if (this.switchLabeledRules // do this check for every block if '->' (Switch Labeled Rules)
&& stmt instanceof Block) {
Block block = (Block) stmt;
if (block.doesNotCompleteNormally()) {
return BREAKING;
}
//JLS 12 15.28.1 Given a switch expression, if the switch block consists of switch labeled rules,
//then it is a compile-time error if any switch labeled block can complete normally.
blockScope.problemReporter().switchExpressionSwitchLabeledBlockCompletesNormally(block);
}
return FALLTHROUGH;
}
@Override
public boolean checkNPE(BlockScope skope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) {
if ((this.nullStatus & FlowInfo.NULL) != 0)
skope.problemReporter().expressionNullReference(this);
else if ((this.nullStatus & FlowInfo.POTENTIALLY_NULL) != 0)
skope.problemReporter().expressionPotentialNullReference(this);
return true; // all checking done
}
private void computeNullStatus(FlowInfo flowInfo, FlowContext flowContext) {
boolean precomputed = this.resultExpressionNullStatus.size() > 0;
if (!precomputed)
this.resultExpressionNullStatus.add(this.resultExpressions.get(0).nullStatus(flowInfo, flowContext)); int status = this.resultExpressions.get(0).nullStatus(flowInfo, flowContext);
int combinedStatus = status;
boolean identicalStatus = true;
for (int i = 1, l = this.resultExpressions.size(); i < l; ++i) {
if (!precomputed)
this.resultExpressionNullStatus.add(this.resultExpressions.get(i).nullStatus(flowInfo, flowContext));
int tmp = this.resultExpressions.get(i).nullStatus(flowInfo, flowContext);
identicalStatus &= status == tmp;
combinedStatus |= tmp;
}
if (identicalStatus) {
this.nullStatus = status;
return;
}
status = Expression.computeNullStatus(0, combinedStatus);
if (status > 0)
this.nullStatus = status;
}
@Override
protected void completeNormallyCheck(BlockScope blockScope) {
if (this.switchLabeledRules) return; // already taken care in getFallThroughState()
int sz = this.statements != null ? this.statements.length : 0;
if (sz == 0) return;
/* JLS 12 15.28.1
* If, on the other hand, the switch block consists of switch labeled statement groups, then it is a
* compile-time error if either the last statement in the switch block can complete normally, or the
* switch block includes one or more switch labels at the end.
*/
Statement lastNonCaseStmt = null;
Statement firstTrailingCaseStmt = null;
for (int i = sz - 1; i >= 0; i--) {
Statement stmt = this.statements[sz - 1];
if (stmt instanceof CaseStatement)
firstTrailingCaseStmt = stmt;
else {
lastNonCaseStmt = stmt;
break;
}
}
if (lastNonCaseStmt != null) {
if (!lastNonCaseStmt.doesNotCompleteNormally())
blockScope.problemReporter().switchExpressionLastStatementCompletesNormally(lastNonCaseStmt);
else if (lastNonCaseStmt instanceof ContinueStatement || lastNonCaseStmt instanceof ReturnStatement) {
blockScope.problemReporter().switchExpressionIllegalLastStatement(lastNonCaseStmt);
}
}
if (firstTrailingCaseStmt != null) {
blockScope.problemReporter().switchExpressionTrailingSwitchLabels(firstTrailingCaseStmt);
}
}
@Override
protected boolean needToCheckFlowInAbsenceOfDefaultBranch() { // JLS 12 16.1.8
return !this.switchLabeledRules;
}
@Override
public Expression[] getPolyExpressions() {
List<Expression> polys = new ArrayList<>();
for (Expression e : this.resultExpressions) {
Expression[] ea = e.getPolyExpressions();
if (ea == null || ea.length ==0) continue;
polys.addAll(Arrays.asList(ea));
}
return polys.toArray(new Expression[0]);
}
@Override
public boolean isPertinentToApplicability(TypeBinding targetType, MethodBinding method) {
for (Expression e : this.resultExpressions) {
if (!e.isPertinentToApplicability(targetType, method))
return false;
}
return true;
}
@Override
public boolean isPotentiallyCompatibleWith(TypeBinding targetType, Scope scope1) {
for (Expression e : this.resultExpressions) {
if (!e.isPotentiallyCompatibleWith(targetType, scope1))
return false;
}
return true;
}
@Override
public boolean isFunctionalType() {
for (Expression e : this.resultExpressions) {
if (e.isFunctionalType()) // return true even for one functional type
return true;
}
return false;
}
@Override
public int nullStatus(FlowInfo flowInfo, FlowContext flowContext) {
if ((this.implicitConversion & TypeIds.BOXING) != 0)
return FlowInfo.NON_NULL;
return this.nullStatus;
}
@Override
protected void statementGenerateCode(BlockScope currentScope, CodeStream codeStream, Statement statement) {
if (!(statement instanceof Expression && ((Expression) statement).isTrulyExpression())
|| statement instanceof Assignment
|| statement instanceof MessageSend
|| (statement instanceof SwitchStatement && !(statement instanceof SwitchExpression))) {
super.statementGenerateCode(currentScope, codeStream, statement);
return;
}
Expression expression1 = (Expression) statement;
expression1.generateCode(currentScope, codeStream, true /* valueRequired */);
}
private TypeBinding createType(int typeId) {
TypeBinding type = TypeBinding.wellKnownType(this.scope, typeId);
return type != null ? type : this.scope.getJavaLangObject();
}
private LocalVariableBinding addTypeStackVariable(CodeStream codeStream, TypeBinding type, int typeId, int index, int resolvedPosition) {
char[] name = CharOperation.concat(SECRET_YIELD_VALUE_NAME, String.valueOf(index).toCharArray());
type = type != null ? type : createType(typeId);
LocalVariableBinding lvb =
new LocalVariableBinding(
name,
type,
ClassFileConstants.AccDefault,
false);
lvb.setConstant(Constant.NotAConstant);
lvb.useFlag = LocalVariableBinding.USED;
lvb.resolvedPosition = resolvedPosition;
// if (this.offset > 0xFFFF) { // no more than 65535 words of locals // TODO - also the cumulative at MethodScope
// problemReporter().noMoreAvailableSpaceForLocal(
// local,
// local.declaration == null ? (ASTNode)methodScope().referenceContext : local.declaration);
// }
this.scope.addLocalVariable(lvb);
lvb.declaration = new LocalDeclaration(name, 0, 0);
return lvb;
}
private int getNextOffset(LocalVariableBinding local) {
int delta = ((TypeBinding.equalsEquals(local.type, TypeBinding.LONG)) || (TypeBinding.equalsEquals(local.type, TypeBinding.DOUBLE))) ?
2 : 1;
return local.resolvedPosition + delta;
}
private void processTypesBindingsOnStack(CodeStream codeStream) {
int count = 0;
int nextResolvedPosition = this.scope.offset;
if (!codeStream.switchSaveTypeBindings.empty()) {
this.typesOnStack = new ArrayList<>();
int index = 0;
Stack<TypeBinding> typeStack = new Stack<>();
int sz = codeStream.switchSaveTypeBindings.size();
for (int i = codeStream.lastSwitchCumulativeSyntheticVars; i < sz; ++i) {
typeStack.add(codeStream.switchSaveTypeBindings.get(i));
}
while (!typeStack.empty()) {
TypeBinding type = typeStack.pop();
LocalVariableBinding lvb = addTypeStackVariable(codeStream, type, TypeIds.T_undefined, index++, nextResolvedPosition);
nextResolvedPosition = getNextOffset(lvb);
this.typesOnStack.add(lvb);
codeStream.store(lvb, false);
codeStream.addVariable(lvb);
++count;
}
}
// now keep a position reserved for yield result value
this.yieldResolvedPosition = nextResolvedPosition;
nextResolvedPosition += ((TypeBinding.equalsEquals(this.resolvedType, TypeBinding.LONG)) ||
(TypeBinding.equalsEquals(this.resolvedType, TypeBinding.DOUBLE))) ?
2 : 1;
codeStream.lastSwitchCumulativeSyntheticVars += count + 1; // 1 for yield var
int delta = nextResolvedPosition - this.scope.offset;
this.scope.adjustLocalVariablePositions(delta, false);
}
public void loadStoredTypesAndKeep(CodeStream codeStream) {
List<LocalVariableBinding> tos = this.typesOnStack;
int sz = tos != null ? tos.size() : 0;
int index = sz - 1;
while (index >= 0) {
LocalVariableBinding lvb = tos.get(index--);
codeStream.load(lvb);
// lvb.recordInitializationEndPC(codeStream.position);
// codeStream.removeVariable(lvb);
}
}
private void removeStoredTypes(CodeStream codeStream) {
List<LocalVariableBinding> tos = this.typesOnStack;
int sz = tos != null ? tos.size() : 0;
int index = sz - 1;
while (index >= 0) {
LocalVariableBinding lvb = tos.get(index--);
codeStream.removeVariable(lvb);
}
}
@Override
public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) {
int tmp = 0;
if (this.containsTry) {
tmp = codeStream.lastSwitchCumulativeSyntheticVars;
processTypesBindingsOnStack(codeStream);
}
super.generateCode(currentScope, codeStream);
if (this.containsTry) {
removeStoredTypes(codeStream);
codeStream.lastSwitchCumulativeSyntheticVars = tmp;
}
if (!valueRequired) {
// switch expression is saved to a variable that is not used. We need to pop the generated value from the stack
switch(postConversionType(currentScope).id) {
case TypeIds.T_long :
case TypeIds.T_double :
codeStream.pop2();
break;
case TypeIds.T_void :
break;
default :
codeStream.pop();
break;
}
}
}
protected boolean computeConversions(BlockScope blockScope, TypeBinding targetType) {
boolean ok = true;
for (int i = 0, l = this.resultExpressions.size(); i < l; ++i) {
ok &= computeConversionsResultExpressions(blockScope, targetType, this.originalValueResultExpressionTypes[i], this.resultExpressions.get(i));
}
return ok;
}
private boolean computeConversionsResultExpressions(BlockScope blockScope, TypeBinding targetType, TypeBinding resultExpressionType,
Expression resultExpression) {
if (resultExpressionType != null && resultExpressionType.isValidBinding()) {
if (resultExpression.isConstantValueOfTypeAssignableToType(resultExpressionType, targetType)
|| resultExpressionType.isCompatibleWith(targetType)) {
resultExpression.computeConversion(blockScope, targetType, resultExpressionType);
if (resultExpressionType.needsUncheckedConversion(targetType)) {
blockScope.problemReporter().unsafeTypeConversion(resultExpression, resultExpressionType, targetType);
}
if (resultExpression instanceof CastExpression
&& (resultExpression.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(blockScope, targetType, (CastExpression) resultExpression);
}
} else if (isBoxingCompatible(resultExpressionType, targetType, resultExpression, blockScope)) {
resultExpression.computeConversion(blockScope, targetType, resultExpressionType);
if (resultExpression instanceof CastExpression
&& (resultExpression.bits & (ASTNode.UnnecessaryCast|ASTNode.DisableUnnecessaryCastCheck)) == 0) {
CastExpression.checkNeedForAssignedCast(blockScope, targetType, (CastExpression) resultExpression);
}
} else {
blockScope.problemReporter().typeMismatchError(resultExpressionType, targetType, resultExpression, null);
return false;
}
}
return true;
}
class OOBLFlagger extends ASTVisitor {
Set<String> labelDecls;
Set<BreakStatement> referencedBreakLabels;
Set<ContinueStatement> referencedContinueLabels;
public OOBLFlagger(SwitchExpression se) {
this.labelDecls = new HashSet<>();
this.referencedBreakLabels = new HashSet<>();
this.referencedContinueLabels = new HashSet<>();
}
@Override
public boolean visit(SwitchExpression switchExpression, BlockScope blockScope) {
return true;
}
private void checkForOutofBoundLabels(BlockScope blockScope) {
try {
for (BreakStatement bs : this.referencedBreakLabels) {
if (bs.label == null || bs.label.length == 0)
continue;
if (!this.labelDecls.contains(new String(bs.label)))
blockScope.problemReporter().switchExpressionsBreakOutOfSwitchExpression(bs);
}
for (ContinueStatement cs : this.referencedContinueLabels) {
if (cs.label == null || cs.label.length == 0)
continue;
if (!this.labelDecls.contains(new String(cs.label)))
blockScope.problemReporter().switchExpressionsContinueOutOfSwitchExpression(cs);
}
} catch (EmptyStackException e) {
// ignore
}
}
@Override
public void endVisit(SwitchExpression switchExpression, BlockScope blockScope) {
checkForOutofBoundLabels(blockScope);
}
@Override
public boolean visit(BreakStatement breakStatement, BlockScope blockScope) {
if (breakStatement.label != null && breakStatement.label.length != 0)
this.referencedBreakLabels.add(breakStatement);
return true;
}
@Override
public boolean visit(ContinueStatement continueStatement, BlockScope blockScope) {
if (continueStatement.label != null && continueStatement.label.length != 0)
this.referencedContinueLabels.add(continueStatement);
return true;
}
@Override
public boolean visit(LabeledStatement stmt, BlockScope blockScope) {
if (stmt.label != null && stmt.label.length != 0)
this.labelDecls.add(new String(stmt.label));
return true;
}
@Override
public boolean visit(TypeDeclaration stmt, BlockScope blockScope) {
return false;
}
}
@Override
public TypeBinding resolveType(BlockScope upperScope) {
return resolveTypeInternal(upperScope);
}
public TypeBinding resolveTypeInternal(BlockScope upperScope) {
try {
int resultExpressionsCount;
if (this.constant != Constant.NotAConstant) {
this.constant = Constant.NotAConstant;
// A switch expression is a poly expression if it appears in an assignment context or an invocation context (5.2, 5.3).
// Otherwise, it is a standalone expression.
if (this.expressionContext == ASSIGNMENT_CONTEXT || this.expressionContext == INVOCATION_CONTEXT) {
for (Expression e : this.resultExpressions) {
//Where a poly switch expression appears in a context of a particular kind with target type T,
//its result expressions similarly appear in a context of the same kind with target type T.
e.setExpressionContext(this.expressionContext);
e.setExpectedType(this.expectedType);
}
}
resolve(upperScope);
if (this.statements == null || this.statements.length == 0) {
// Report Error JLS 13 15.28.1 The switch block must not be empty.
upperScope.problemReporter().switchExpressionEmptySwitchBlock(this);
return null;
}
resultExpressionsCount = this.resultExpressions != null ? this.resultExpressions.size() : 0;
if (resultExpressionsCount == 0) {
// Report Error JLS 13 15.28.1
// It is a compile-time error if a switch expression has no result expressions.
upperScope.problemReporter().switchExpressionNoResultExpressions(this);
return null;
}
this.traverse(new OOBLFlagger(this), upperScope);
if (this.originalValueResultExpressionTypes == null) {
this.originalValueResultExpressionTypes = new TypeBinding[resultExpressionsCount];
this.finalValueResultExpressionTypes = new TypeBinding[resultExpressionsCount];
for (int i = 0; i < resultExpressionsCount; ++i) {
this.finalValueResultExpressionTypes[i] = this.originalValueResultExpressionTypes[i] =
this.resultExpressions.get(i).resolvedType;
}
}
if (isPolyExpression()) { //The type of a poly switch expression is the same as its target type.
if (this.expectedType == null || !this.expectedType.isProperType(true)) {
return new PolyTypeBinding(this);
}
return this.resolvedType = computeConversions(this.scope, this.expectedType) ? this.expectedType : null;
}
// fall through
} else {
// re-resolving of poly expression:
resultExpressionsCount = this.resultExpressions != null ? this.resultExpressions.size() : 0;
if (resultExpressionsCount == 0)
return this.resolvedType = null; // error flagging would have been done during the earlier phase.
for (int i = 0; i < resultExpressionsCount; i++) {
Expression resultExpr = this.resultExpressions.get(i);
if (resultExpr.resolvedType == null || resultExpr.resolvedType.kind() == Binding.POLY_TYPE) {
this.finalValueResultExpressionTypes[i] = this.originalValueResultExpressionTypes[i] =
resultExpr.resolveTypeExpecting(upperScope, this.expectedType);
}
// This is a kludge and only way completion can tell this node to resolve all
// resultExpressions. Ideal solution is to remove all other expressions except
// the one that contain the completion node.
if (this.resolveAll) continue;
if (resultExpr.resolvedType == null || !resultExpr.resolvedType.isValidBinding())
return this.resolvedType = null;
}
this.resolvedType = computeConversions(this.scope, this.expectedType) ? this.expectedType : null;
// fall through
}
if (resultExpressionsCount == 1)
return this.resolvedType = this.originalValueResultExpressionTypes[0];
boolean typeUniformAcrossAllArms = true;
TypeBinding tmp = this.originalValueResultExpressionTypes[0];
for (int i = 1, l = this.originalValueResultExpressionTypes.length; i < l; ++i) {
TypeBinding originalType = this.originalValueResultExpressionTypes[i];
if (originalType != null && TypeBinding.notEquals(tmp, originalType)) {
typeUniformAcrossAllArms = false;
break;
}
}
// If the result expressions all have the same type (which may be the null type),
// then that is the type of the switch expression.
if (typeUniformAcrossAllArms) {
tmp = this.originalValueResultExpressionTypes[0];
for (int i = 1; i < resultExpressionsCount; ++i) {
if (this.originalValueResultExpressionTypes[i] != null)
tmp = NullAnnotationMatching.moreDangerousType(tmp, this.originalValueResultExpressionTypes[i]);
}
return this.resolvedType = tmp;
}
boolean typeBbolean = true;
for (TypeBinding t : this.originalValueResultExpressionTypes) {
if (t != null)
typeBbolean &= t.id == T_boolean || t.id == T_JavaLangBoolean;
}
LookupEnvironment env = this.scope.environment();
/*
* Otherwise, if the type of each result expression is boolean or Boolean,
* an unboxing conversion (5.1.8) is applied to each result expression of type Boolean,
* and the switch expression has type boolean.
*/
if (typeBbolean) {
for (int i = 0; i < resultExpressionsCount; ++i) {
if (this.originalValueResultExpressionTypes[i].id == T_boolean) continue;
this.finalValueResultExpressionTypes[i] = env.computeBoxingType(this.originalValueResultExpressionTypes[i]);
this.resultExpressions.get(i).computeConversion(this.scope, this.finalValueResultExpressionTypes[i], this.originalValueResultExpressionTypes[i]);
}
return this.resolvedType = TypeBinding.BOOLEAN;
}
/*
* Otherwise, if the type of each result expression is convertible to a numeric type (5.1.8), the type
* of the switch expression is given by numeric promotion (5.6.3) applied to the result expressions.
*/
boolean typeNumeric = true;
TypeBinding resultNumeric = null;
HashSet<TypeBinding> typeSet = new HashSet<>();
/* JLS 13 5.6 Numeric Contexts
* An expression appears in a numeric context if it is one of:....
* ...8. a result expression of a standalone switch expression (15.28.1),
* where all the result expressions are convertible to a numeric type
* If any expression is of a reference type, it is subjected to unboxing conversion (5.1.8).
*/
for (int i = 0; i < resultExpressionsCount; ++i) {
TypeBinding originalType = this.originalValueResultExpressionTypes[i];
if (originalType == null) continue;
tmp = originalType.isNumericType() ? originalType : env.computeBoxingType(originalType);
if (!tmp.isNumericType()) {
typeNumeric = false;
break;
}
typeSet.add(TypeBinding.wellKnownType(this.scope, tmp.id));
}
if (typeNumeric) {
/* If any result expression is of type double, then other result expressions that are not of type double
* are widened to double.
* Otherwise, if any result expression is of type float, then other result expressions that are not of
* type float are widened to float.
* Otherwise, if any result expression is of type long, then other result expressions that are not of
* type long are widened to long.
*/
TypeBinding[] dfl = new TypeBinding[]{// do not change the order JLS 13 5.6
TypeBinding.DOUBLE,
TypeBinding.FLOAT,
TypeBinding.LONG};
for (TypeBinding binding : dfl) {
if (typeSet.contains(binding)) {
resultNumeric = binding;
break;
}
}
/* Otherwise, if any expression appears in a numeric array context or a numeric arithmetic context,
* rather than a numeric choice context, then the promoted type is int and other expressions that are
* not of type int undergo widening primitive conversion to int. - not applicable since numeric choice context.
* [Note: A numeric choice context is a numeric context that is either a numeric conditional expression or
* a standalone switch expression where all the result expressions are convertible to a numeric type.]
*/
/* Otherwise, if any result expression is of type int and is not a constant expression, the other
* result expressions that are not of type int are widened to int.
*/
resultNumeric = resultNumeric != null ? resultNumeric : check_nonconstant_int();
resultNumeric = resultNumeric != null ? resultNumeric : // one among the first few rules applied.
getResultNumeric(typeSet, this.originalValueResultExpressionTypes); // check the rest
typeSet = null; // hey gc!
for (int i = 0; i < resultExpressionsCount; ++i) {
this.resultExpressions.get(i).computeConversion(this.scope,
resultNumeric, this.originalValueResultExpressionTypes[i]);
this.finalValueResultExpressionTypes[i] = resultNumeric;
}
// After the conversion(s), if any, value set conversion (5.1.13) is then applied to each result expression.
return this.resolvedType = resultNumeric;
}
/* Otherwise, boxing conversion (5.1.7) is applied to each result expression that has a primitive type,
* after which the type of the switch expression is the result of applying capture conversion (5.1.10)
* to the least upper bound (4.10.4) of the types of the result expressions.
*/
for (int i = 0; i < resultExpressionsCount; ++i) {
TypeBinding finalType = this.finalValueResultExpressionTypes[i];
if (finalType != null && finalType.isBaseType())
this.finalValueResultExpressionTypes[i] = env.computeBoxingType(finalType);
}
TypeBinding commonType = this.scope.lowerUpperBound(this.finalValueResultExpressionTypes);
if (commonType != null) {
for (int i = 0, l = this.resultExpressions.size(); i < l; ++i) {
if (this.originalValueResultExpressionTypes[i] == null) continue;
this.resultExpressions.get(i).computeConversion(this.scope, commonType, this.originalValueResultExpressionTypes[i]);
this.finalValueResultExpressionTypes[i] = commonType;
}
return this.resolvedType = commonType.capture(this.scope, this.sourceStart, this.sourceEnd);
}
this.scope.problemReporter().switchExpressionIncompatibleResultExpressions(this);
return null;
} finally {
if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block
}
}
private TypeBinding check_nonconstant_int() {
for (int i = 0, l = this.resultExpressions.size(); i < l; ++i) {
Expression e = this.resultExpressions.get(i);
TypeBinding type = this.originalValueResultExpressionTypes[i];
if (type != null && type.id == T_int && e.constant == Constant.NotAConstant)
return TypeBinding.INT;
}
return null;
}
private boolean areAllIntegerResultExpressionsConvertibleToTargetType(TypeBinding targetType) {
for (int i = 0, l = this.resultExpressions.size(); i < l; ++i) {
Expression e = this.resultExpressions.get(i);
TypeBinding t = this.originalValueResultExpressionTypes[i];
if (!TypeBinding.equalsEquals(t, TypeBinding.INT)) continue;
if (!e.isConstantValueOfTypeAssignableToType(t, targetType))
return false;
}
return true;
}
@Override
public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) {
flowInfo = super.analyseCode(currentScope, flowContext, flowInfo);
this.resultExpressionNullStatus = new ArrayList<>(0);
final CompilerOptions compilerOptions = currentScope.compilerOptions();
if (compilerOptions.enableSyntacticNullAnalysisForFields) {
for (Expression re : this.resultExpressions) {
this.resultExpressionNullStatus.add(re.nullStatus(flowInfo, flowContext));
// wipe information that was meant only for this result expression:
flowContext.expireNullCheckedFieldInfo();
}
}
computeNullStatus(flowInfo, flowContext);
return flowInfo;
}
@Override
protected void addSecretTryResultVariable() {
if (this.containsTry) {
this.hiddenYield =
new LocalVariableBinding(
SwitchExpression.SECRET_YIELD_VALUE_NAME,
null,
ClassFileConstants.AccDefault,
false);
this.hiddenYield.setConstant(Constant.NotAConstant);
this.hiddenYield.useFlag = LocalVariableBinding.USED;
this.scope.addLocalVariable(this.hiddenYield);
this.hiddenYield.declaration = new LocalDeclaration(SECRET_YIELD_VALUE_NAME, 0, 0);
}
}
private TypeBinding check_csb(Set<TypeBinding> typeSet, TypeBinding candidate) {
if (!typeSet.contains(candidate))
return null;
TypeBinding[] allowedTypes = SwitchExpression.type_map.get(candidate);
Set<TypeBinding> allowedSet = Arrays.stream(allowedTypes).collect(Collectors.toSet());
if (!allowedSet.containsAll(typeSet))
return null;
return areAllIntegerResultExpressionsConvertibleToTargetType(candidate) ?
candidate : null;
}
private TypeBinding getResultNumeric(Set<TypeBinding> typeSet, TypeBinding[] armTypes) {
// note: if an expression has a type integer, then it will be a constant
// since non-constant integers are already processed before reaching here.
/* Otherwise, if any expression is of type short, and every other expression is either of type short,
* or of type byte, or a constant expression of type int with a value that is representable in the
* type short, then T is short, the byte expressions undergo widening primitive conversion to short,
* and the int expressions undergo narrowing primitive conversion to short.\
*
* Otherwise, if any expression is of type byte, and every other expression is either of type byte or a
* constant expression of type int with a value that is representable in the type byte, then T is byte
* and the int expressions undergo narrowing primitive conversion to byte.
*
* Otherwise, if any expression is of type char, and every other expression is either of type char or a
* constant expression of type int with a value that is representable in the type char, then T is char
* and the int expressions undergo narrowing primitive conversion to char.
*
* Otherwise, T is int and all the expressions that are not of type int undergo widening
* primitive conversion to int.
*/
// DO NOT Change the order below [as per JLS 13 5.6 ].
TypeBinding[] csb = new TypeBinding[] {TypeBinding.SHORT, TypeBinding.BYTE, TypeBinding.CHAR};
for (TypeBinding c : csb) {
TypeBinding result = check_csb(typeSet, c);
if (result != null)
return result;
}
/* Otherwise, all the result expressions that are not of type int are widened to int. */
return TypeBinding.INT;
}
@Override
public boolean isPolyExpression() {
if (this.isPolyExpression)
return true;
// JLS 13 15.28.1 A switch expression is a poly expression if it appears in an assignment context or
// an invocation context (5.2, 5.3). Otherwise, it is a standalone expression.
return this.isPolyExpression = this.expressionContext == ASSIGNMENT_CONTEXT ||
this.expressionContext == INVOCATION_CONTEXT;
}
@Override
public boolean isTrulyExpression() {
return true;
}
@Override
public boolean isCompatibleWith(TypeBinding left, Scope skope) {
if (!isPolyExpression())
return super.isCompatibleWith(left, skope);
for (Expression e : this.resultExpressions) {
if (!e.isCompatibleWith(left, skope))
return false;
}
return true;
}
@Override
public boolean isBoxingCompatibleWith(TypeBinding targetType, Scope skope) {
if (!isPolyExpression())
return super.isBoxingCompatibleWith(targetType, skope);
for (Expression e : this.resultExpressions) {
if (!(e.isCompatibleWith(targetType, skope) || e.isBoxingCompatibleWith(targetType, skope)))
return false;
}
return true;
}
@Override
public boolean sIsMoreSpecific(TypeBinding s, TypeBinding t, Scope skope) {
if (super.sIsMoreSpecific(s, t, skope))
return true;
if (!isPolyExpression())
return false;
for (Expression e : this.resultExpressions) {
if (!e.sIsMoreSpecific(s, t, skope))
return false;
}
return true;
}
@Override
public TypeBinding expectedType() {
return this.expectedType;
}
@Override
public void traverse(
ASTVisitor visitor,
BlockScope blockScope) {
if (visitor.visit(this, blockScope)) {
this.expression.traverse(visitor, blockScope);
if (this.statements != null) {
int statementsLength = this.statements.length;
for (int i = 0; i < statementsLength; i++)
this.statements[i].traverse(visitor, this.scope);
}
}
visitor.endVisit(this, blockScope);
}
}