diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java index 354576a4d49..97839ed39d5 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ClassFile.java @@ -48,7 +48,7 @@ import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.compiler.ast.*; -import org.eclipse.jdt.internal.compiler.ast.CaseStatement.ResolvedCase; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement.LabelExpression; import org.eclipse.jdt.internal.compiler.ast.SwitchStatement.SingletonBootstrap; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.*; @@ -3613,8 +3613,8 @@ private int generateBootstrapMethods(List bootStrapMethodsList) { } } else if (o instanceof String) { localContentsOffset = addBootStrapStringConcatEntry(localContentsOffset, (String) o, fPtr); - } else if (o instanceof ResolvedCase) { - localContentsOffset = addBootStrapTypeCaseConstantEntry(localContentsOffset, (ResolvedCase) o, fPtr); + } else if (o instanceof LabelExpression) { + localContentsOffset = addBootStrapTypeCaseConstantEntry(localContentsOffset, (LabelExpression) o, fPtr); } else if (o instanceof TypeBinding) { localContentsOffset = addClassDescBootstrap(localContentsOffset, (TypeBinding) o, fPtr); } else if (o instanceof SingletonBootstrap sb) { @@ -3807,7 +3807,7 @@ private int addBootStrapRecordEntry(int localContentsOffset, TypeDeclaration typ } return localContentsOffset; } - private int addBootStrapTypeCaseConstantEntry(int localContentsOffset, ResolvedCase caseConstant, Map fPtr) { + private int addBootStrapTypeCaseConstantEntry(int localContentsOffset, LabelExpression caseConstant, Map fPtr) { final int contentsEntries = 10; if (contentsEntries + localContentsOffset >= this.contents.length) { resizeContents(contentsEntries); @@ -3852,7 +3852,8 @@ private int addBootStrapTypeCaseConstantEntry(int localContentsOffset, ResolvedC this.contents[localContentsOffset++] = (byte) (idx >> 8); this.contents[localContentsOffset++] = (byte) idx; - idx = this.constantPool.literalIndex(caseConstant.c.stringValue()); + String enumerator = caseConstant.expression instanceof QualifiedNameReference qnr ? new String(qnr.tokens[qnr.tokens.length - 1]) : caseConstant.expression.toString(); + idx = this.constantPool.literalIndex(enumerator); this.contents[localContentsOffset++] = (byte) (idx >> 8); this.contents[localContentsOffset++] = (byte) idx; @@ -3930,7 +3931,7 @@ private int addSingletonBootstrap(int localContentsOffset, SingletonBootstrap sb } private int addBootStrapTypeSwitchEntry(int localContentsOffset, SwitchStatement switchStatement, Map fPtr) { - CaseStatement.ResolvedCase[] constants = switchStatement.otherConstants; + CaseStatement.LabelExpression[] constants = switchStatement.labelExpressions; int numArgs = constants.length; final int contentsEntries = 10 + (numArgs * 2); int indexFortypeSwitch = fPtr.get(ClassFile.TYPESWITCH_STRING); @@ -3952,16 +3953,16 @@ private int addBootStrapTypeSwitchEntry(int localContentsOffset, SwitchStatement this.contents[numArgsLocation++] = (byte) (numArgs >> 8); this.contents[numArgsLocation] = (byte) numArgs; localContentsOffset += 2; - for (CaseStatement.ResolvedCase c : constants) { + for (CaseStatement.LabelExpression c : constants) { if (c.isPattern()) { int typeOrDynIndex; - if (c.e.resolvedType.isPrimitiveType()) { + if (c.expression.resolvedType.isPrimitiveType()) { // Dynamic for Class.getPrimitiveClass(Z) or such typeOrDynIndex = this.constantPool.literalIndexForDynamic(c.primitivesBootstrapIdx, - c.t.signature(), + c.type.signature(), ConstantPool.JavaLangClassSignature); } else { - char[] typeName = c.t.constantPoolName(); + char[] typeName = c.type.constantPoolName(); typeOrDynIndex = this.constantPool.literalIndexForType(typeName); } this.contents[localContentsOffset++] = (byte) (typeOrDynIndex >> 8); @@ -3972,26 +3973,26 @@ private int addBootStrapTypeSwitchEntry(int localContentsOffset, SwitchStatement ConstantPool.JAVA_LANG_ENUM_ENUMDESC); this.contents[localContentsOffset++] = (byte) (typeIndex >> 8); this.contents[localContentsOffset++] = (byte) typeIndex; - } else if ((c.e instanceof StringLiteral)||(c.c instanceof StringConstant)) { + } else if ((c.expression instanceof StringLiteral)||(c.constant instanceof StringConstant)) { int intValIdx = - this.constantPool.literalIndex(c.c.stringValue()); + this.constantPool.literalIndex(c.constant.stringValue()); this.contents[localContentsOffset++] = (byte) (intValIdx >> 8); this.contents[localContentsOffset++] = (byte) intValIdx; } else { - if (c.e instanceof NullLiteral) continue; - int valIdx = switch (c.t.id) { + if (c.expression instanceof NullLiteral) continue; + int valIdx = switch (c.type.id) { case TypeIds.T_boolean -> // Dynamic for Boolean.getStaticFinal(TRUE|FALSE) : this.constantPool.literalIndexForDynamic(c.primitivesBootstrapIdx, - c.c.booleanValue() ? BooleanConstant.TRUE_STRING : BooleanConstant.FALSE_STRING, + c.constant.booleanValue() ? BooleanConstant.TRUE_STRING : BooleanConstant.FALSE_STRING, ConstantPool.JavaLangBooleanSignature); case TypeIds.T_byte, TypeIds.T_char, TypeIds.T_short, TypeIds.T_int -> this.constantPool.literalIndex(c.intValue()); case TypeIds.T_long -> - this.constantPool.literalIndex(c.c.longValue()); + this.constantPool.literalIndex(c.constant.longValue()); case TypeIds.T_float -> - this.constantPool.literalIndex(c.c.floatValue()); + this.constantPool.literalIndex(c.constant.floatValue()); case TypeIds.T_double -> - this.constantPool.literalIndex(c.c.doubleValue()); + this.constantPool.literalIndex(c.constant.doubleValue()); default -> throw new IllegalArgumentException("Switch has unexpected type: "+switchStatement); //$NON-NLS-1$ }; @@ -4019,25 +4020,23 @@ private int addBootStrapEnumSwitchEntry(int localContentsOffset, SwitchStatement // u2 num_bootstrap_arguments int numArgsLocation = localContentsOffset; - CaseStatement.ResolvedCase[] constants = switchStatement.otherConstants; + CaseStatement.LabelExpression[] constants = switchStatement.labelExpressions; int numArgs = constants.length; if (switchStatement.containsNull) --numArgs; this.contents[numArgsLocation++] = (byte) (numArgs >> 8); this.contents[numArgsLocation] = (byte) numArgs; localContentsOffset += 2; - for (CaseStatement.ResolvedCase c : constants) { + for (CaseStatement.LabelExpression c : constants) { if (c.isPattern()) { char[] typeName = switchStatement.expression.resolvedType.constantPoolName(); int typeIndex = this.constantPool.literalIndexForType(typeName); this.contents[localContentsOffset++] = (byte) (typeIndex >> 8); this.contents[localContentsOffset++] = (byte) typeIndex; } else { - if (c.e instanceof NullLiteral) continue; - String s = c.e instanceof QualifiedNameReference qnr ? // handle superfluously qualified enumerator. - new String(qnr.tokens[qnr.tokens.length-1]) : c.e.toString(); - int intValIdx = - this.constantPool.literalIndex(s); + if (c.expression instanceof NullLiteral) continue; + String enumerator = c.expression instanceof QualifiedNameReference qnr ? new String(qnr.tokens[qnr.tokens.length - 1]) : c.expression.toString(); + int intValIdx = this.constantPool.literalIndex(enumerator); this.contents[localContentsOffset++] = (byte) (intValIdx >> 8); this.contents[localContentsOffset++] = (byte) intValIdx; } @@ -6370,7 +6369,7 @@ public int recordBootstrapMethod(SwitchStatement switchStatement) { this.bootstrapMethods.add(switchStatement); return this.bootstrapMethods.size() - 1; } - public int recordBootstrapMethod(ResolvedCase resolvedCase) { + public int recordBootstrapMethod(LabelExpression resolvedCase) { if (this.bootstrapMethods == null) { this.bootstrapMethods = new ArrayList<>(); } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/BreakStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/BreakStatement.java index 265de6f1705..c219bd09c2a 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/BreakStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/BreakStatement.java @@ -23,7 +23,6 @@ public class BreakStatement extends BranchStatement { - public boolean isSynthetic; public BreakStatement(char[] label, int sourceStart, int e) { super(label, sourceStart, e); } @@ -115,10 +114,4 @@ public boolean doesNotCompleteNormally() { public boolean canCompleteNormally() { return false; } - - -@Override -protected boolean doNotReportUnreachable() { - return this.isSynthetic; -} } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java index e2d7b8141fe..10468434b4f 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/CaseStatement.java @@ -10,13 +10,13 @@ * * Contributors: * IBM Corporation - initial API and implementation + * Advantest R & D - Enhanced Switches v2.0 *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.stream.Stream; +import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.ast.Pattern.PrimitiveConversionRoute; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -29,7 +29,6 @@ import org.eclipse.jdt.internal.compiler.impl.IntConstant; import org.eclipse.jdt.internal.compiler.impl.JavaFeature; import org.eclipse.jdt.internal.compiler.impl.StringConstant; -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; @@ -39,14 +38,13 @@ public class CaseStatement extends Statement { - public BranchLabel targetLabel; public Expression[] constantExpressions; // case with multiple expressions - if you want a under-the-hood view, use peeledLabelExpressions() public BranchLabel[] targetLabels; // for multiple expressions public boolean isSwitchRule = false; public SwitchStatement swich; // owning switch - public int typeSwitchIndex; // for the first pattern among this.constantExpressions + public int labelExpressionOrdinal; // for the first pattern among this.constantExpressions public CaseStatement(Expression[] constantExpressions, int sourceStart, int sourceEnd) { this.constantExpressions = constantExpressions; @@ -54,194 +52,225 @@ public CaseStatement(Expression[] constantExpressions, int sourceStart, int sour this.sourceEnd = sourceEnd; } +public static class LabelExpression { + public Constant constant; + public Expression expression; + public TypeBinding type; // For ease of access. This.e contains the type binding anyway. + public int index; + private int intValue; + private final boolean isPattern; + private final boolean isQualifiedEnum; + public int enumDescIdx; + public int classDescIdx; + public int primitivesBootstrapIdx; // index for a bootstrap method to args to indy typeSwitch for primitives + + LabelExpression(Constant c, Expression e, TypeBinding t, int index, boolean isQualifiedEnum) { + this.constant = c; + this.expression = e; + this.type = t; + this.index = index; + this.intValue = c.typeID() == TypeIds.T_JavaLangString ? c.stringValue().hashCode() : c.intValue(); + this.isPattern = e instanceof Pattern; + this.isQualifiedEnum = isQualifiedEnum; + } + + public int intValue() { return this.intValue; } + public boolean isPattern() { return this.isPattern; } + public boolean isQualifiedEnum() { return this.isQualifiedEnum; } + + @Override + public String toString() { + return "case " + this.expression + " [CONSTANT=" + this.constant + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } +} + /** Provide an under-the-hood view of label expressions, peeling away any abstractions that package many expressions as one * @return flattened array of label expressions */ public Expression [] peeledLabelExpressions() { Expression [] constants = Expression.NO_EXPRESSIONS; for (Expression e : this.constantExpressions) { - if (e instanceof Pattern p1) { - constants = Stream.concat(Arrays.stream(constants), Arrays.stream(p1.getAlternatives())).toArray(Expression[]::new); - } else { + if (e instanceof Pattern p) + constants = Stream.concat(Arrays.stream(constants), Arrays.stream(p.getAlternatives())).toArray(Expression[]::new); + else constants = Stream.concat(Arrays.stream(constants), Stream.of(e)).toArray(Expression[]::new); - } } return constants; } -@Override -public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { - int nullPatternCount = 0; - for (int i = 0, length = this.constantExpressions.length; i < length; i++) { - Expression e = this.constantExpressions[i]; - CompilerOptions compilerOptions = currentScope.compilerOptions(); - long sourceLevel = compilerOptions.sourceLevel; - boolean enablePreviewFeatures = compilerOptions.enablePreviewFeatures; - if (!JavaFeature.UNNAMMED_PATTERNS_AND_VARS.isSupported(sourceLevel, enablePreviewFeatures)) { - for (LocalVariableBinding local : e.bindingsWhenTrue()) { - local.useFlag = LocalVariableBinding.USED; // these are structurally required even if not touched - } - } - nullPatternCount += e instanceof NullLiteral ? 1 : 0; - if (i > 0 && (e instanceof Pattern) && !JavaFeature.UNNAMMED_PATTERNS_AND_VARS.isSupported(sourceLevel, enablePreviewFeatures)) { - if (!(i == nullPatternCount && e instanceof TypePattern)) - currentScope.problemReporter().IllegalFallThroughToPattern(e); - } - flowInfo = analyseConstantExpression(currentScope, flowContext, flowInfo, e); - if (nullPatternCount > 0 && e instanceof TypePattern) { - LocalVariableBinding binding = ((TypePattern) e).local.binding; - if (binding != null) - flowInfo.markNullStatus(binding, FlowInfo.POTENTIALLY_NULL); - } - } +private boolean essentiallyQualifiedEnumerator(Expression e, TypeBinding selectorType) { // "Essentially" as in not "superfluously" qualified. + return e instanceof QualifiedNameReference qnr && qnr.binding instanceof FieldBinding field + && (field.modifiers & ClassFileConstants.AccEnum) != 0 && !TypeBinding.equalsEquals(e.resolvedType, selectorType); // <<-- essential qualification +} - return flowInfo; +private void checkDuplicateDefault(BlockScope scope, ASTNode node) { + if (this.swich.defaultCase != null) + scope.problemReporter().duplicateDefaultCase(node); + else if (this.swich.totalPattern != null) + scope.problemReporter().illegalTotalPatternWithDefault(this); + this.swich.defaultCase = this; } -private FlowInfo analyseConstantExpression( - BlockScope currentScope, - FlowContext flowContext, - FlowInfo flowInfo, - Expression e) { - if (e.constant == Constant.NotAConstant - && !e.resolvedType.isEnum()) { - boolean caseNullorDefaultAllowed = - JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(currentScope.compilerOptions()) - && (e instanceof NullLiteral || e instanceof FakeDefaultLiteral || e instanceof Pattern); - if (!caseNullorDefaultAllowed) - currentScope.problemReporter().caseExpressionMustBeConstant(e); - if (e instanceof NullLiteral && flowContext.associatedNode instanceof SwitchStatement) { - Expression switchValue = ((SwitchStatement) flowContext.associatedNode).expression; - if (switchValue != null && switchValue.nullStatus(flowInfo, flowContext) == FlowInfo.NON_NULL) { - currentScope.problemReporter().unnecessaryNullCaseInSwitchOverNonNull(this); + +private Constant resolveConstantLabel(BlockScope scope, TypeBinding caseType, TypeBinding selectorType, Expression expression) { + + if (expression instanceof NullLiteral) { + if (!caseType.isCompatibleWith(selectorType, scope)) + scope.problemReporter().caseConstantIncompatible(TypeBinding.NULL, selectorType, expression); + return IntConstant.fromValue(-1); + } + + if (expression instanceof StringLiteral) { + if (selectorType.id == T_JavaLangString) + return expression.constant; + scope.problemReporter().caseConstantIncompatible(expression.resolvedType, selectorType, expression); + return Constant.NotAConstant; + } + + CompilerOptions options = scope.compilerOptions(); + if (caseType.isEnum() && caseType.isCompatibleWith(selectorType)) { + if (((expression.bits & ASTNode.ParenthesizedMASK) >> ASTNode.ParenthesizedSHIFT) != 0) { + scope.problemReporter().enumConstantsCannotBeSurroundedByParenthesis(expression); + return Constant.NotAConstant; + } + if (expression instanceof NameReference reference && reference.binding instanceof FieldBinding field) { + if ((field.modifiers & ClassFileConstants.AccEnum) == 0) + scope.problemReporter().enumSwitchCannotTargetField(reference, field); + else if (reference instanceof QualifiedNameReference) { + if (options.complianceLevel < ClassFileConstants.JDK21) + scope.problemReporter().cannotUseQualifiedEnumConstantInCaseLabel(reference, field); + else if (!TypeBinding.equalsEquals(caseType, selectorType)) { + this.swich.switchBits |= SwitchStatement.QualifiedEnum; + return StringConstant.fromValue(CharOperation.toString(reference.getName())); + } } + return IntConstant.fromValue(field.original().id + 1); // (ordinal value + 1) zero should not be returned see bug 141810 } + scope.problemReporter().caseExpressionMustBeConstant(expression); + return Constant.NotAConstant; } - return e.analyseCode(currentScope, flowContext, flowInfo); -} -/** - * No-op : should use resolveCase(...) instead. - */ -@Override -public void resolve(BlockScope scope) { - // no-op : should use resolveCase(...) instead. -} -public static class ResolvedCase { - static final ResolvedCase[] UnresolvedCase = new ResolvedCase[0]; - public Constant c; - public Expression e; - public TypeBinding t; // For ease of access. This.e contains the type binding anyway. - public int index; - private int intValue; - private final boolean isPattern; - private final boolean isQualifiedEnum; - public int enumDescIdx; - public int classDescIdx; - public int primitivesBootstrapIdx; // index for a bootstrap method to args to indy typeSwitch for primitives - ResolvedCase(Constant c, Expression e, TypeBinding t, int index, boolean isQualifiedEnum) { - this.c = c; - this.e = e; - this.t= t; - this.index = index; - if (c.typeID() == TypeIds.T_JavaLangString) { - this.intValue = c.stringValue().hashCode(); - } else { - this.intValue = c.intValue(); - } - this.isPattern = e instanceof Pattern; - this.isQualifiedEnum = isQualifiedEnum; + if (this.swich.isNonTraditional && selectorType.isBaseType() && !expression.isConstantValueOfTypeAssignableToType(caseType, selectorType)) { + scope.problemReporter().caseConstantIncompatible(caseType, selectorType, expression); + return Constant.NotAConstant; } - public int intValue() { - return this.intValue; + + if (expression.isConstantValueOfTypeAssignableToType(caseType, selectorType) || caseType.isCompatibleWith(selectorType)) { + if (expression.constant == Constant.NotAConstant) + scope.problemReporter().caseExpressionMustBeConstant(expression); + return expression.constant; + } + + boolean boxing = !JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(options) || this.swich.isAllowedType(selectorType); + if (boxing && isBoxingCompatible(caseType, selectorType, expression, scope)) { + if (expression.constant == Constant.NotAConstant) + scope.problemReporter().caseExpressionMustBeConstant(expression); + return expression.constant; } - public boolean isPattern() { - return this.isPattern; + scope.problemReporter().caseConstantIncompatible(expression.resolvedType, selectorType, expression); + return Constant.NotAConstant; +} + +private Constant resolvePatternLabel(BlockScope scope, TypeBinding caseType, TypeBinding selectorType, Pattern pattern, boolean isUnguarded) { + + Constant constant = IntConstant.fromValue(this.swich.labelExpressionIndex); + + if (pattern instanceof RecordPattern) + this.swich.containsRecordPatterns = true; + + if (isUnguarded) { + this.swich.caseLabelElementTypes.add(caseType); + this.swich.caseLabelElements.add(pattern); } - public boolean isQualifiedEnum() { - return this.isQualifiedEnum; + + if (!caseType.isReifiable()) { + if (!pattern.isApplicable(selectorType, scope, pattern)) + return Constant.NotAConstant; + } else if (caseType.isValidBinding()) { // already complained if invalid + if (Pattern.findPrimitiveConversionRoute(caseType, selectorType, scope) == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) { + if (caseType.isPrimitiveType() && !JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) { + scope.problemReporter().unexpectedTypeinSwitchPattern(caseType, pattern); + return Constant.NotAConstant; + } else if (!pattern.checkCastTypesCompatibility(scope, caseType, selectorType, null, false)) { + scope.problemReporter().typeMismatchError(selectorType, caseType, pattern, null); + return Constant.NotAConstant; + } + } else { + this.swich.isPrimitiveSwitch = true; + } } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append("case "); //$NON-NLS-1$ - builder.append(this.e); - builder.append(" [CONSTANT="); //$NON-NLS-1$ - builder.append(this.c); - builder.append("]"); //$NON-NLS-1$ - return builder.toString(); + if (pattern.coversType(selectorType, scope)) { + this.swich.switchBits |= SwitchStatement.Exhaustive; + pattern.isTotalTypeNode = true; + if (pattern.isUnconditional(selectorType, scope)) // unguarded is implied from 'coversType()' above + this.swich.totalPattern = pattern; } + return constant; } -/** - * Returns the constant intValue or ordinal for enum constants. If constant is NotAConstant, then answers Float.MIN_VALUE - */ -public ResolvedCase[] resolveCase(BlockScope scope, TypeBinding switchExpressionType, SwitchStatement switchStatement) { - this.swich = switchStatement; +@Override +public void resolve(BlockScope scope) { + + if (this.swich == null) + return; + + TypeBinding selectorType = (this.swich.switchBits & SwitchStatement.InvalidSelector) != 0 ? null : this.swich.expression.resolvedType; // to inhibit secondary errors. + this.labelExpressionOrdinal = this.swich.labelExpressionIndex; + this.swich.cases[this.swich.caseCount++] = this; if (this.isSwitchRule) this.swich.switchBits |= SwitchStatement.LabeledRules; else this.swich.switchBits |= SwitchStatement.LabeledBlockStatementGroup; - if ((this.swich.switchBits & (SwitchStatement.LabeledRules | SwitchStatement.LabeledBlockStatementGroup)) == (SwitchStatement.LabeledRules | SwitchStatement.LabeledBlockStatementGroup)) { + if ((this.swich.switchBits & (SwitchStatement.LabeledRules | SwitchStatement.LabeledBlockStatementGroup)) == (SwitchStatement.LabeledRules | SwitchStatement.LabeledBlockStatementGroup)) scope.problemReporter().arrowColonMixup(this); - } scope.enclosingCase = this; // record entering in a switch case block if (this.constantExpressions == Expression.NO_EXPRESSIONS) { - checkDuplicateDefault(scope, switchStatement, this); - return ResolvedCase.UnresolvedCase; + checkDuplicateDefault(scope, this); + return; } - switchStatement.cases[switchStatement.caseCount++] = this; - - List cases = new ArrayList<>(); int count = 0; int nullCaseCount = 0; - boolean hasResolveErrors = false; for (Expression e : this.constantExpressions) { count++; if (e instanceof FakeDefaultLiteral) { - switchStatement.containsPatterns = switchStatement.isNonTraditional = true; - checkDuplicateDefault(scope, switchStatement, this.constantExpressions.length > 1 ? e : this); + this.swich.containsPatterns = this.swich.isNonTraditional = true; + checkDuplicateDefault(scope, this.constantExpressions.length > 1 ? e : this); if (count != 2 || nullCaseCount < 1) scope.problemReporter().patternSwitchCaseDefaultOnlyAsSecond(e); continue; } if (e instanceof NullLiteral) { - switchStatement.containsNull = switchStatement.isNonTraditional = true; - if (switchStatement.nullCase == null) - switchStatement.nullCase = this; + this.swich.containsNull = this.swich.isNonTraditional = true; + if (this.swich.nullCase == null) + this.swich.nullCase = this; nullCaseCount++; if (count > 1 && nullCaseCount < 2) scope.problemReporter().patternSwitchNullOnlyOrFirstWithDefault(e); } // tag constant name with enum type for privileged access to its members - if (switchExpressionType != null && switchExpressionType.isEnum() && (e instanceof SingleNameReference)) - ((SingleNameReference) e).setActualReceiverType((ReferenceBinding)switchExpressionType); + if (selectorType != null && selectorType.isEnum() && (e instanceof SingleNameReference)) + ((SingleNameReference) e).setActualReceiverType((ReferenceBinding)selectorType); e.setExpressionContext(ExpressionContext.TESTING_CONTEXT); if (e instanceof Pattern p) { - switchStatement.containsPatterns = switchStatement.isNonTraditional = true; - p.setOuterExpressionType(switchExpressionType); + this.swich.containsPatterns = this.swich.isNonTraditional = true; + p.setOuterExpressionType(selectorType); } TypeBinding caseType = e.resolveType(scope); - - if (caseType == null || switchExpressionType == null) { - hasResolveErrors = true; + if (caseType == null || selectorType == null) continue; - } if (caseType.isValidBinding()) { if (e instanceof Pattern) { for (Pattern p : ((Pattern) e).getAlternatives()) { - Constant con = resolveCasePattern(scope, p.resolvedType, switchExpressionType, switchStatement, p, ((Pattern) e).isUnguarded()); - if (con != Constant.NotAConstant) { - int index = switchStatement.constantIndex++; - cases.add(new ResolvedCase(con, p, p.resolvedType, index, false)); - } + Constant constant = resolvePatternLabel(scope, p.resolvedType, selectorType, p, ((Pattern) e).isUnguarded()); + if (constant != Constant.NotAConstant) + this.swich.gatherLabelExpression(new LabelExpression(constant, p, p.resolvedType, this.swich.labelExpressionIndex, false)); } } else { // check from ยง14.11.1 (JEP 455): @@ -249,185 +278,58 @@ public ResolvedCase[] resolveCase(BlockScope scope, TypeBinding switchExpression // - [...] // - if T is one of long, float, double, or boolean, the type of the case constant is T. // - if T is one of Long, Float, Double, or Boolean, the type of the case constant is, respectively, long, float, double, or boolean. - TypeBinding expectedCaseType = switchExpressionType; - if (switchExpressionType.isBoxedPrimitiveType()) { - expectedCaseType = scope.environment().computeBoxingType(switchExpressionType); // in this case it's actually 'computeUnboxingType()' - } - switch (expectedCaseType.id) { - case TypeIds.T_long, TypeIds.T_float, TypeIds.T_double, TypeIds.T_boolean -> { - if (caseType.id != expectedCaseType.id) { - scope.problemReporter().caseExpressionWrongType(e, switchExpressionType, expectedCaseType); - continue; + if (caseType.id != T_null) { + TypeBinding expectedCaseType = selectorType.isBoxedPrimitiveType() ? selectorType.unboxedType() : selectorType; + switch (expectedCaseType.id) { + case TypeIds.T_long, TypeIds.T_float, TypeIds.T_double, TypeIds.T_boolean -> { + if (caseType.id != expectedCaseType.id) { + scope.problemReporter().caseExpressionWrongType(e, selectorType, expectedCaseType); + continue; + } + selectorType = expectedCaseType; } - switchExpressionType = expectedCaseType; } } - // - Constant con = resolveConstantExpression(scope, caseType, switchExpressionType, switchStatement, e, cases); - if (con != Constant.NotAConstant) { - int index = this == switchStatement.nullCase && e instanceof NullLiteral ? - -1 : switchStatement.constantIndex++; - cases.add(new ResolvedCase(con, e, caseType, index, false)); + Constant constant = resolveConstantLabel(scope, caseType, selectorType, e); + if (constant != Constant.NotAConstant) { + int index = -1; + boolean isQualifiedEnum = false; + if (essentiallyQualifiedEnumerator(e, selectorType)) + isQualifiedEnum = true; + else if (!(e instanceof NullLiteral)) + index = this.swich.labelExpressionIndex; + this.swich.gatherLabelExpression(new LabelExpression(constant, e, caseType, index, isQualifiedEnum)); } } } } - return hasResolveErrors ? ResolvedCase.UnresolvedCase : cases.toArray(new ResolvedCase[cases.size()]); -} - -/** - * Precondition: this is a default case. - * Check if (a) another default or (b) a total type pattern has been recorded already - */ -private void checkDuplicateDefault(BlockScope scope, SwitchStatement switchStatement, ASTNode node) { - if (switchStatement.defaultCase != null) { - scope.problemReporter().duplicateDefaultCase(node); - } else if ((switchStatement.switchBits & SwitchStatement.TotalPattern) != 0) { - scope.problemReporter().illegalTotalPatternWithDefault(this); - } - - // remember the default case into the associated switch statement - // on error the last default will be the selected one ... - switchStatement.defaultCase = this; } @Override -public LocalVariableBinding[] bindingsWhenTrue() { - LocalVariableBinding [] variables = NO_VARIABLES; - for (Expression e : this.constantExpressions) { - variables = LocalVariableBinding.merge(variables, e.bindingsWhenTrue()); - } - return variables; -} - -public Constant resolveConstantExpression(BlockScope scope, - TypeBinding caseType, - TypeBinding switchType, - SwitchStatement switchStatement, - Expression expression, - List cases) { - - CompilerOptions options = scope.compilerOptions(); - boolean patternSwitchAllowed = JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(options); - if (patternSwitchAllowed) { - if (expression instanceof Pattern) { - throw new AssertionError("Unexpected control flow"); //$NON-NLS-1$ - } else if (expression instanceof NullLiteral) { - if (!caseType.isCompatibleWith(switchType, scope)) { - scope.problemReporter().caseConstantIncompatible(TypeBinding.NULL, switchType, expression); - } - switchStatement.switchBits |= SwitchStatement.NullCase; - return IntConstant.fromValue(-1); - } else if (expression instanceof FakeDefaultLiteral) { - // do nothing - } else { - if (switchStatement.isNonTraditional) { - if (switchType.isBaseType() && !expression.isConstantValueOfTypeAssignableToType(caseType, switchType)) { - scope.problemReporter().caseConstantIncompatible(caseType, switchType, expression); - return Constant.NotAConstant; - } - } - - } - } - boolean boxing = !patternSwitchAllowed || - switchStatement.isAllowedType(switchType); - - if (expression.isConstantValueOfTypeAssignableToType(caseType, switchType) - ||(caseType.isCompatibleWith(switchType) - && !(expression instanceof StringLiteral))) { - if (caseType.isEnum()) { - if (((expression.bits & ASTNode.ParenthesizedMASK) >> ASTNode.ParenthesizedSHIFT) != 0) { - scope.problemReporter().enumConstantsCannotBeSurroundedByParenthesis(expression); - } - - if (expression instanceof NameReference - && (expression.bits & ASTNode.RestrictiveFlagMASK) == Binding.FIELD) { - NameReference reference = (NameReference) expression; - FieldBinding field = reference.fieldBinding(); - if ((field.modifiers & ClassFileConstants.AccEnum) == 0) { - scope.problemReporter().enumSwitchCannotTargetField(reference, field); - } else if (reference instanceof QualifiedNameReference) { - if (options.complianceLevel < ClassFileConstants.JDK21) { - scope.problemReporter().cannotUseQualifiedEnumConstantInCaseLabel(reference, field); - } else if (!TypeBinding.equalsEquals(caseType, switchType)) { - switchStatement.switchBits |= SwitchStatement.QualifiedEnum; - StringConstant constant = (StringConstant) StringConstant.fromValue(new String(field.name)); - cases.add(new ResolvedCase(constant, expression, caseType, -1, true)); - return Constant.NotAConstant; - } - } - return IntConstant.fromValue(field.original().id + 1); // (ordinal value + 1) zero should not be returned see bug 141810 - } - } else { - return expression.constant; - } - } else if (boxing && isBoxingCompatible(caseType, switchType, expression, scope)) { - // constantExpression.computeConversion(scope, caseType, switchExpressionType); - do not report boxing/unboxing conversion - return expression.constant; - } - scope.problemReporter().caseConstantIncompatible(expression.resolvedType, switchType, expression); - return Constant.NotAConstant; -} - -private Constant resolveCasePattern(BlockScope scope, TypeBinding caseType, TypeBinding switchExpressionType, SwitchStatement switchStatement, Pattern e, boolean isUnguarded) { - - Constant constant = Constant.NotAConstant; - - TypeBinding type = e.resolvedType; - - if (type != null) { - constant = IntConstant.fromValue(switchStatement.constantIndex); - switchStatement.caseLabelElements.add(e); - if (e instanceof RecordPattern) - switchStatement.containsRecordPatterns = true; - - if (isUnguarded) - switchStatement.caseLabelElementTypes.add(type); +public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { + if (!JavaFeature.UNNAMMED_PATTERNS_AND_VARS.isSupported(currentScope.compilerOptions())) + for (LocalVariableBinding local : bindingsWhenTrue()) + local.useFlag = LocalVariableBinding.USED; // these are structurally required even if not touched - TypeBinding expressionType = switchStatement.expression.resolvedType; - // The following code is copied from InstanceOfExpression#resolve() - // But there are enough differences to warrant a copy - if (!type.isReifiable()) { - if (!e.isApplicable(switchExpressionType, scope, e)) { - return Constant.NotAConstant; - } - } else if (type.isValidBinding()) { - // if not a valid binding, an error has already been reported for unresolved type - if (Pattern.findPrimitiveConversionRoute(type, expressionType, scope) == PrimitiveConversionRoute.NO_CONVERSION_ROUTE) { - if (type.isPrimitiveType() && !JavaFeature.PRIMITIVES_IN_PATTERNS.isSupported(scope.compilerOptions())) { - scope.problemReporter().unexpectedTypeinSwitchPattern(type, e); - return Constant.NotAConstant; - } else if (!e.checkCastTypesCompatibility(scope, type, expressionType, null, false)) { - scope.problemReporter().typeMismatchError(expressionType, type, e, null); - return Constant.NotAConstant; - } - } else { - this.swich.isPrimitiveSwitch = true; - } - } - if (e.coversType(expressionType, scope)) { - switchStatement.switchBits |= SwitchStatement.Exhaustive; - e.isTotalTypeNode = true; - if (e.isUnconditional(expressionType, scope)) // unguarded is implied from 'coversType()' above - switchStatement.switchBits |= SwitchStatement.TotalPattern; - if (switchStatement.nullCase == null) - constant = IntConstant.fromValue(-1); + for (Expression e : this.constantExpressions) { + if (e instanceof NullLiteral && flowContext.associatedNode instanceof SwitchStatement swichStatement) { + Expression switchValue = swichStatement.expression; + if (switchValue != null && switchValue.nullStatus(flowInfo, flowContext) == FlowInfo.NON_NULL) + currentScope.problemReporter().unnecessaryNullCaseInSwitchOverNonNull(this); } + flowInfo = e.analyseCode(currentScope, flowContext, flowInfo); } - return constant; + return flowInfo; } @Override public void generateCode(BlockScope currentScope, CodeStream codeStream) { - if ((this.bits & ASTNode.IsReachable) == 0) { + if ((this.bits & ASTNode.IsReachable) == 0) return; - } int pc = codeStream.position; if (this.targetLabels != null) { - for (BranchLabel label : this.targetLabels) { + for (BranchLabel label : this.targetLabels) label.place(); - } } if (this.targetLabel != null) this.targetLabel.place(); @@ -438,7 +340,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { BranchLabel matchFailLabel = new BranchLabel(codeStream); Pattern pattern = (Pattern) this.constantExpressions[0]; - codeStream.load(this.swich.dispatchPatternCopy); + codeStream.load(this.swich.selector); pattern.generateCode(currentScope, codeStream, patternMatchLabel, matchFailLabel); codeStream.goto_(patternMatchLabel); matchFailLabel.place(); @@ -450,43 +352,48 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { */ final LocalVariableBinding[] bindingsWhenTrue = pattern.bindingsWhenTrue(); Stream.of(bindingsWhenTrue).forEach(v->v.recordInitializationEndPC(codeStream.position)); - int caseIndex = this.typeSwitchIndex + pattern.getAlternatives().length; + codeStream.load(this.swich.selector); + int caseIndex = this.labelExpressionOrdinal + pattern.getAlternatives().length; codeStream.loadInt(this.swich.nullProcessed ? caseIndex - 1 : caseIndex); - codeStream.store(this.swich.restartIndexLocal, false); codeStream.goto_(this.swich.switchPatternRestartTarget); Stream.of(bindingsWhenTrue).forEach(v->v.recordInitializationStartPC(codeStream.position)); } patternMatchLabel.place(); } else { - if (this.swich.containsNull) { + if (this.swich.containsNull) this.swich.nullProcessed |= true; - } } codeStream.recordPositionsFrom(pc, this.sourceStart); } +@Override +public LocalVariableBinding[] bindingsWhenTrue() { + LocalVariableBinding [] variables = NO_VARIABLES; + for (Expression e : this.constantExpressions) + variables = LocalVariableBinding.merge(variables, e.bindingsWhenTrue()); + return variables; +} + @Override public StringBuilder printStatement(int tab, StringBuilder output) { printIndent(tab, output); - if (this.constantExpressions == Expression.NO_EXPRESSIONS) { + if (this.constantExpressions == Expression.NO_EXPRESSIONS) output.append("default"); //$NON-NLS-1$ - } else { + else { output.append("case "); //$NON-NLS-1$ - for (int i = 0, l = this.constantExpressions.length; i < l; ++i) { + for (int i = 0, length = this.constantExpressions.length; i < length; ++i) { this.constantExpressions[i].printExpression(0, output); - if (i < l -1) output.append(','); + if (i < length -1) output.append(','); } } - output.append(this.isSwitchRule ? " ->" : " :"); //$NON-NLS-1$ //$NON-NLS-2$ - return output; + return output.append(this.isSwitchRule ? " ->" : " :"); //$NON-NLS-1$ //$NON-NLS-2$ } @Override public void traverse(ASTVisitor visitor, BlockScope blockScope) { if (visitor.visit(this, blockScope)) { - for (Expression e : this.constantExpressions) { + for (Expression e : this.constantExpressions) e.traverse(visitor, blockScope); - } } visitor.endVisit(this, blockScope); } diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Statement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Statement.java index f8ec362accb..a8b1db7c524 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Statement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/Statement.java @@ -385,8 +385,7 @@ public int complainIfUnreachable(FlowInfo flowInfo, BlockScope scope, int previo this.bits &= ~ASTNode.IsReachable; if (flowInfo == FlowInfo.DEAD_END) { if (previousComplaintLevel < COMPLAINED_UNREACHABLE) { - if (!this.doNotReportUnreachable()) - scope.problemReporter().unreachableCode(this); + scope.problemReporter().unreachableCode(this); if (endOfBlock) scope.checkUnclosedCloseables(flowInfo, null, null, null); } @@ -403,9 +402,6 @@ public int complainIfUnreachable(FlowInfo flowInfo, BlockScope scope, int previo return previousComplaintLevel; } -protected boolean doNotReportUnreachable() { - return false; -} /** * Generate invocation arguments, considering varargs methods */ diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java index e78cdf021c2..811ebee69d8 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchExpression.java @@ -181,12 +181,6 @@ protected void reportMissingEnumConstantCase(BlockScope upperScope, FieldBinding upperScope.problemReporter().missingEnumConstantCase(this, enumConstant); } @Override - protected int getFallThroughState(Statement stmt, BlockScope blockScope) { - if (stmt.isTrulyExpression() || stmt instanceof ThrowStatement) - return BREAKING; - return stmt.canCompleteNormally() ? FALLTHROUGH : BREAKING; - } - @Override public boolean checkNPE(BlockScope skope, FlowContext flowContext, FlowInfo flowInfo, int ttlForFieldCheck) { if ((this.nullStatus & FlowInfo.NULL) != 0) skope.problemReporter().expressionNullReference(this); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java index 2c0a7a98040..40a96991762 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/ast/SwitchStatement.java @@ -30,10 +30,9 @@ import java.util.List; import java.util.Set; import java.util.function.Function; -import java.util.function.IntPredicate; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; -import org.eclipse.jdt.internal.compiler.ast.CaseStatement.ResolvedCase; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement.LabelExpression; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; import org.eclipse.jdt.internal.compiler.codegen.CaseLabel; @@ -74,15 +73,17 @@ public static record SingletonBootstrap(String id, char[] selector, char[] signa public BlockScope scope; public int explicitDeclarations; public BranchLabel breakLabel; - public CaseStatement[] cases; + + public CaseStatement[] cases; // all cases *including* default public CaseStatement defaultCase; public CaseStatement nullCase; // convenience pointer for pattern switches public int blockStart; - public int caseCount; - int[] constants; - int[] constMapping; - // Any non int constants - public ResolvedCase[] otherConstants; + public int caseCount; // count of all cases *including* default + + public static final LabelExpression[] NO_LABEL_EXPRESSIONS = new LabelExpression[0]; + public LabelExpression[] labelExpressions = NO_LABEL_EXPRESSIONS; + public int labelExpressionIndex = 0; + public int nConstants; public int switchBits; @@ -101,18 +102,13 @@ public static record SingletonBootstrap(String id, char[] selector, char[] signa // Other bits public final static int LabeledRules = ASTNode.Bit1; - public final static int NullCase = ASTNode.Bit2; - public final static int TotalPattern = ASTNode.Bit3; - public final static int Exhaustive = ASTNode.Bit4; - public final static int QualifiedEnum = ASTNode.Bit5; - public final static int LabeledBlockStatementGroup = ASTNode.Bit6; + public final static int InvalidSelector = ASTNode.Bit2; + public final static int Exhaustive = ASTNode.Bit3; + public final static int QualifiedEnum = ASTNode.Bit4; + public final static int LabeledBlockStatementGroup = ASTNode.Bit5; // for switch on strings - private static final char[] SecretStringVariableName = " switchDispatchString".toCharArray(); //$NON-NLS-1$ - - // for patterns in switch - /* package */ static final char[] SecretPatternVariableName = " switchDispatchPattern".toCharArray(); //$NON-NLS-1$ - private static final char[] SecretPatternRestartIndexName = " switchPatternRestartIndex".toCharArray(); //$NON-NLS-1$ + private static final char[] SecretSelectorVariableName = " selector".toCharArray(); //$NON-NLS-1$ public SyntheticMethodBinding synthetic; // use for switch on enums types @@ -120,17 +116,12 @@ public static record SingletonBootstrap(String id, char[] selector, char[] signa int preSwitchInitStateIndex = -1; int mergedInitStateIndex = -1; - Statement[] duplicateCases = null; - int duplicateCaseCounter = 0; - private LocalVariableBinding dispatchStringCopy = null; - LocalVariableBinding dispatchPatternCopy = null; - LocalVariableBinding restartIndexLocal = null; + LocalVariableBinding selector = null; /* package */ boolean isNonTraditional = false; /* package */ boolean isPrimitiveSwitch = false; /* package */ List caseLabelElements = new ArrayList<>(0);//TODO: can we remove this? public List caseLabelElementTypes = new ArrayList<>(0); - int constantIndex = 0; class Node { TypeBinding type; @@ -375,30 +366,7 @@ public boolean visit(TNode node) { return false; // no need to visit further. } } - protected int getFallThroughState(Statement stmt, BlockScope blockScope) { - if ((this.switchBits & LabeledRules) != 0) { - if (stmt.isTrulyExpression() || stmt instanceof ThrowStatement) - return BREAKING; - if (!stmt.canCompleteNormally()) - return BREAKING; - if (stmt instanceof Block block) { - BreakStatement breakStatement = new BreakStatement(null, block.sourceEnd -1, block.sourceEnd); - breakStatement.isSynthetic = true; // suppress dead code flagging - codegen will not generate dead code anyway - int length = block.statements == null ? 0 : block.statements.length; - if (length == 0) { - block.statements = new Statement[] {breakStatement}; - block.scope = this.scope; // (upper scope) see Block.resolve() for similar - } else { - Statement[] newArray = new Statement[length + 1]; - System.arraycopy(block.statements, 0, newArray, 0, length); - newArray[length] = breakStatement; - block.statements = newArray; - } - return BREAKING; - } - } - return FALLTHROUGH; - } + protected boolean needToCheckFlowInAbsenceOfDefaultBranch() { return !this.isExhaustive(); } @@ -419,7 +387,6 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl FlowInfo caseInits = FlowInfo.DEAD_END; // in case of statements before the first case this.preSwitchInitStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); - int caseIndex = 0; if (this.statements != null) { int initialComplaintLevel = (flowInfo.reachMode() & FlowInfo.UNREACHABLE) != 0 ? Statement.COMPLAINED_FAKE_REACHABLE : Statement.NOT_COMPLAINED; int complaintLevel = initialComplaintLevel; @@ -427,9 +394,8 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl int prevCaseStmtIndex = -100; for (int i = 0, max = this.statements.length; i < max; i++) { Statement statement = this.statements[i]; - if ((caseIndex < this.caseCount) && (statement == this.cases[caseIndex])) { // statement is a case - this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block - caseIndex++; + if (statement instanceof CaseStatement caseStatement) { + this.scope.enclosingCase = caseStatement; // record entering in a switch case block if (prevCaseStmtIndex == i - 1) { if (this.statements[prevCaseStmtIndex].containsPatternVariable()) this.scope.problemReporter().illegalFallthroughFromAPattern(this.statements[prevCaseStmtIndex]); @@ -438,52 +404,40 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, Fl if (fallThroughState == FALLTHROUGH && complaintLevel <= NOT_COMPLAINED) { if (statement.containsPatternVariable()) this.scope.problemReporter().IllegalFallThroughToPattern(this.scope.enclosingCase); - else if ((statement.bits & ASTNode.DocumentedFallthrough) == 0) { // the case is not fall-through protected by a line comment + else if ((statement.bits & ASTNode.DocumentedFallthrough) == 0) // the case is not fall-through protected by a line comment this.scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase); - } - } - caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); - complaintLevel = initialComplaintLevel; // reset complaint - fallThroughState = this.containsPatterns ? FALLTHROUGH : CASE; - } else if (statement == this.defaultCase) { // statement is the default case - this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block - if (fallThroughState == FALLTHROUGH - && complaintLevel <= NOT_COMPLAINED - && (statement.bits & ASTNode.DocumentedFallthrough) == 0) { - this.scope.problemReporter().possibleFallThroughCase(this.scope.enclosingCase); } caseInits = caseInits.mergedWith(flowInfo.unconditionalInits()); - if ((this.switchBits & LabeledRules) != 0 && this.expression.resolvedType instanceof ReferenceBinding) { - if (this.expression instanceof NameReference) { - // default case does not apply to null => mark the variable being switched over as nonnull: - NameReference reference = (NameReference) this.expression; - if (reference.localVariableBinding() != null) { - caseInits.markAsDefinitelyNonNull(reference.localVariableBinding()); - } else if (reference.lastFieldBinding() != null) { + if (caseStatement.constantExpressions == NO_EXPRESSIONS) { + if ((this.switchBits & LabeledRules) != 0 && this.expression.resolvedType instanceof ReferenceBinding) { + if (this.expression instanceof NameReference) { + // default case does not apply to null => mark the variable being switched over as nonnull: + NameReference reference = (NameReference) this.expression; + if (reference.localVariableBinding() != null) { + caseInits.markAsDefinitelyNonNull(reference.localVariableBinding()); + } else if (reference.lastFieldBinding() != null) { + if (this.scope.compilerOptions().enableSyntacticNullAnalysisForFields) + switchContext.recordNullCheckedFieldReference(reference, 2); // survive this case statement and into the next + } + } else if (this.expression instanceof FieldReference) { if (this.scope.compilerOptions().enableSyntacticNullAnalysisForFields) - switchContext.recordNullCheckedFieldReference(reference, 2); // survive this case statement and into the next + switchContext.recordNullCheckedFieldReference((FieldReference) this.expression, 2); // survive this case statement and into the next } - } else if (this.expression instanceof FieldReference) { - if (this.scope.compilerOptions().enableSyntacticNullAnalysisForFields) - switchContext.recordNullCheckedFieldReference((FieldReference) this.expression, 2); // survive this case statement and into the next } } complaintLevel = initialComplaintLevel; // reset complaint fallThroughState = this.containsPatterns ? FALLTHROUGH : CASE; } else { - fallThroughState = getFallThroughState(statement, currentScope); // reset below if needed + fallThroughState = (this.switchBits & LabeledRules) != 0 || statement.doesNotCompleteNormally() ? BREAKING : FALLTHROUGH; // reset below if needed } if ((complaintLevel = statement.complainIfUnreachable(caseInits, this.scope, complaintLevel, true)) < Statement.COMPLAINED_UNREACHABLE) { caseInits = statement.analyseCode(this.scope, switchContext, caseInits); - if (caseInits == FlowInfo.DEAD_END) { + if (caseInits == FlowInfo.DEAD_END) fallThroughState = ESCAPING; - } - if (compilerOptions.enableSyntacticNullAnalysisForFields) { + if (compilerOptions.enableSyntacticNullAnalysisForFields) switchContext.expireNullCheckedFieldInfo(); - } - if (compilerOptions.analyseResourceLeaks) { + if (compilerOptions.analyseResourceLeaks) FakedTrackingVariable.cleanUpUnassigned(this.scope, statement, caseInits, false); - } } } } @@ -513,15 +467,11 @@ else if ((statement.bits & ASTNode.DocumentedFallthrough) == 0) { // the case is private boolean isNullHostile() { if (this.containsNull) return false; - if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0) { + if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0) return true; - } else if (this.expression.resolvedType != null - && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum())) { + if (this.expression.resolvedType != null && (this.expression.resolvedType.id == T_JavaLangString || this.expression.resolvedType.isEnum())) return true; - } else if ((this.switchBits & (LabeledRules|NullCase)) == LabeledRules && this.totalPattern == null) { - return true; - } - return false; + return this.totalPattern == null; } /** @@ -537,9 +487,7 @@ private boolean isNullHostile() { public void generateCodeForStringSwitch(BlockScope currentScope, CodeStream codeStream) { try { - if ((this.bits & IsReachable) == 0) { - return; - } + int pc = codeStream.position; class StringSwitchCase implements Comparable { @@ -593,17 +541,17 @@ public String toString() { "FB" and "Ea" producing the same hashcode values, but still belonging in different case statements. First, produce the two branch labels pertaining to the case statements - And the three string cases and use the this.constMapping to get the correct branch label. + And the three string cases. */ - final boolean hasCases = this.caseCount != 0; - int constSize = hasCases ? this.otherConstants.length : 0; + final boolean hasCases = this.caseCount > 1 || (this.caseCount == 1 && this.defaultCase == null); + int constSize = hasCases ? this.labelExpressions.length : 0; BranchLabel[] sourceCaseLabels = this.gatherLabels(codeStream, new BranchLabel[this.nConstants], BranchLabel::new); StringSwitchCase [] stringCases = new StringSwitchCase[constSize]; // may have to shrink later if multiple strings hash to same code. CaseLabel [] hashCodeCaseLabels = new CaseLabel[constSize]; - this.constants = new int[constSize]; // hashCode() values. + int [] hashCodes = new int[constSize]; for (int i = 0; i < constSize; i++) { - String literal = this.otherConstants[i].c.stringValue(); - stringCases[i] = new StringSwitchCase(literal.hashCode(), literal, sourceCaseLabels[this.constMapping[i]]); + String literal = this.labelExpressions[i].constant.stringValue(); + stringCases[i] = new StringSwitchCase(literal.hashCode(), literal, sourceCaseLabels[i]); hashCodeCaseLabels[i] = new CaseLabel(codeStream); hashCodeCaseLabels[i].tagBits |= BranchLabel.USED; } @@ -614,12 +562,12 @@ public String toString() { for (int i = 0, length = constSize; i < length; ++i) { int hashCode = stringCases[i].hashCode; if (i == 0 || hashCode != lastHashCode) { - lastHashCode = this.constants[uniqHashCount++] = hashCode; + lastHashCode = hashCodes[uniqHashCount++] = hashCode; } } if (uniqHashCount != constSize) { // multiple keys hashed to the same value. - System.arraycopy(this.constants, 0, this.constants = new int[uniqHashCount], 0, uniqHashCount); + System.arraycopy(hashCodes, 0, hashCodes = new int[uniqHashCount], 0, uniqHashCount); System.arraycopy(hashCodeCaseLabels, 0, hashCodeCaseLabels = new CaseLabel[uniqHashCount], 0, uniqHashCount); } int[] sortedIndexes = new int[uniqHashCount]; // hash code are sorted already anyways. @@ -640,11 +588,11 @@ public String toString() { } // generate expression this.expression.generateCode(currentScope, codeStream, true); - codeStream.store(this.dispatchStringCopy, true); // leaves string on operand stack - codeStream.addVariable(this.dispatchStringCopy); + codeStream.store(this.selector, true); // leaves string on operand stack + codeStream.addVariable(this.selector); codeStream.invokeStringHashCode(); if (hasCases) { - codeStream.lookupswitch(defaultCaseLabel, this.constants, sortedIndexes, hashCodeCaseLabels); + codeStream.lookupswitch(defaultCaseLabel, hashCodes, sortedIndexes, hashCodeCaseLabels); for (int i = 0, j = 0, max = constSize; i < max; i++) { int hashCode = stringCases[i].hashCode; if (i == 0 || hashCode != lastHashCode) { @@ -654,7 +602,7 @@ public String toString() { } hashCodeCaseLabels[j++].place(); } - codeStream.load(this.dispatchStringCopy); + codeStream.load(this.selector); codeStream.ldc(stringCases[i].string); codeStream.invokeStringEquals(); codeStream.ifne(stringCases[i].label); @@ -665,28 +613,18 @@ public String toString() { } // generate the switch block statements - int caseIndex = 0; if (this.statements != null) { for (Statement statement : this.statements) { - if ((caseIndex < this.caseCount) && (statement == this.cases[caseIndex])) { // statements[i] is a case - this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block - if (this.preSwitchInitStateIndex != -1) { + if (statement instanceof CaseStatement caseStatement) { + this.scope.enclosingCase = caseStatement; // record entering in a switch case block + if (this.preSwitchInitStateIndex != -1) codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); - } - if (statement == this.defaultCase) { // statements[i] is a case or a default case - defaultCaseLabel.place(); // branch label gets placed by generateCode below. - } - caseIndex++; - } else { - if (statement == this.defaultCase) { // statements[i] is a case or a default case + if (statement == this.defaultCase) defaultCaseLabel.place(); // branch label gets placed by generateCode below. - this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block - if (this.preSwitchInitStateIndex != -1) { - codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); - } - } } - statementGenerateCode(currentScope, codeStream, statement); + statement.generateCode(this.scope, codeStream); + if ((this.switchBits & LabeledRules) != 0 && statement instanceof Block && statement.canCompleteNormally()) + codeStream.goto_(this.breakLabel); } } @@ -695,7 +633,7 @@ public String toString() { codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); codeStream.addDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); } - codeStream.removeVariable(this.dispatchStringCopy); + codeStream.removeVariable(this.selector); if (this.scope != currentScope) { codeStream.exitUserScope(this.scope); } @@ -718,10 +656,10 @@ private T[] gatherLabels(CodeStream codeStream, T[] caseL for (int i = 0, j = 0, max = this.caseCount; i < max; i++) { CaseStatement stmt = this.cases[i]; final Expression[] peeledLabelExpressions = stmt.peeledLabelExpressions(); - int l = peeledLabelExpressions.length; - BranchLabel[] targetLabels = new BranchLabel[l]; + int length = peeledLabelExpressions.length; + BranchLabel[] targetLabels = new BranchLabel[length]; int count = 0; - for (int k = 0; k < l; ++k) { + for (int k = 0; k < length; ++k) { Expression e = peeledLabelExpressions[k]; if (e instanceof FakeDefaultLiteral) continue; targetLabels[count++] = (caseLabels[j] = newLabel.apply(codeStream)); @@ -741,23 +679,22 @@ private T[] gatherLabels(CodeStream codeStream, T[] caseL */ @Override public void generateCode(BlockScope currentScope, CodeStream codeStream) { + if ((this.bits & IsReachable) == 0) + return; + if (this.expression.resolvedType.id == TypeIds.T_JavaLangString && !this.isNonTraditional) { generateCodeForStringSwitch(currentScope, codeStream); return; } + try { - if ((this.bits & IsReachable) == 0) { - return; - } int pc = codeStream.position; - // prepare the labels and constants this.breakLabel.initialize(codeStream); - int constantCount = this.otherConstants == null ? 0 : this.otherConstants.length; CaseLabel[] caseLabels = this.gatherLabels(codeStream, new CaseLabel[this.nConstants], CaseLabel::new); CaseLabel defaultLabel = new CaseLabel(codeStream); - final boolean hasCases = this.caseCount != 0; + final boolean hasCases = this.caseCount > 1 || (this.caseCount == 1 && this.defaultCase == null); if (hasCases) defaultLabel.tagBits |= BranchLabel.USED; if (this.defaultCase != null) { this.defaultCase.targetLabel = defaultLabel; @@ -765,28 +702,41 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { final TypeBinding resolvedType1 = this.expression.resolvedType; boolean valueRequired = false; + int constantCount = this.labelExpressions.length; + int [] constants = new int[constantCount]; if (needPatternDispatchCopy()) { generateCodeSwitchPatternPrologue(currentScope, codeStream); valueRequired = true; - transformConstants(); - } else if (resolvedType1.isEnum()) { - // go through the translation table - codeStream.invoke(Opcodes.OPC_invokestatic, this.synthetic, null /* default declaringClass */); - this.expression.generateCode(currentScope, codeStream, true); - // get enum constant ordinal() - codeStream.invokeEnumOrdinal(resolvedType1.constantPoolName()); - codeStream.iaload(); - if (!hasCases) { - // we can get rid of the generated ordinal value - codeStream.pop(); + for (int i = 0, j = 0, length = this.labelExpressions.length; i < length; ++i) { + if (this.nullCase == null && this.labelExpressions[i].expression == this.totalPattern) + this.labelExpressions[i].index = -1; + constants[i] = this.labelExpressions[i].index - j; + if (this.labelExpressions[i].expression instanceof NullLiteral) + j = 1; // since we yank null out to -1, shift down everything beyond. } - valueRequired = hasCases; } else { - valueRequired = this.expression.constant == Constant.NotAConstant || hasCases; - // generate expression - this.expression.generateCode(currentScope, codeStream, valueRequired); - if (resolvedType1.id == TypeIds.T_JavaLangBoolean) { - codeStream.generateUnboxingConversion(TypeIds.T_boolean); // optimize by avoiding indy typeSwitch + for (int i = 0, length = this.labelExpressions.length; i < length; ++i) + constants[i] = this.labelExpressions[i].intValue(); + if (resolvedType1.isEnum()) { + // go through the translation table + codeStream.invoke(Opcodes.OPC_invokestatic, this.synthetic, null /* default declaringClass */); + this.expression.generateCode(currentScope, codeStream, true); + // get enum constant ordinal() + codeStream.invokeEnumOrdinal(resolvedType1.constantPoolName()); + codeStream.iaload(); + if (!hasCases) { + // we can get rid of the generated ordinal value + codeStream.pop(); + } + valueRequired = hasCases; + } else { + valueRequired = this.expression.constant == Constant.NotAConstant || hasCases; + // generate expression + this.expression.generateCode(currentScope, codeStream, valueRequired); + if (resolvedType1.id == TypeIds.T_JavaLangBoolean) { + codeStream.generateUnboxingConversion(TypeIds.T_boolean); // optimize by avoiding indy + // typeSwitch + } } } // generate the appropriate switch table/lookup bytecode @@ -797,7 +747,7 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { sortedIndexes[i] = i; } int[] localKeysCopy; - System.arraycopy(this.constants, 0, (localKeysCopy = new int[constantCount]), 0, constantCount); + System.arraycopy(constants, 0, (localKeysCopy = new int[constantCount]), 0, constantCount); CodeStream.sort(localKeysCopy, 0, constantCount - 1, sortedIndexes); int max = localKeysCopy[constantCount - 1]; @@ -807,12 +757,11 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { defaultLabel, min, max, - this.constants, + constants, sortedIndexes, - this.constMapping, caseLabels); } else { - codeStream.lookupswitch(defaultLabel, this.constants, sortedIndexes, caseLabels); + codeStream.lookupswitch(defaultLabel, constants, sortedIndexes, caseLabels); } codeStream.recordPositionsFrom(codeStream.position, this.expression.sourceEnd); } else if (valueRequired) { @@ -820,29 +769,16 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { } // generate the switch block statements - int caseIndex = 0; - int typeSwitchIndex = 0; if (this.statements != null) { for (Statement statement : this.statements) { - CaseStatement caseStatement = null; - if ((caseIndex < constantCount) && (statement == this.cases[caseIndex])) { // statements[i] is a case - this.scope.enclosingCase = this.cases[caseIndex]; // record entering in a switch case block - if (this.preSwitchInitStateIndex != -1) { + if (statement instanceof CaseStatement caseStatement) { + this.scope.enclosingCase = caseStatement; // record entering in a switch case block + if (this.preSwitchInitStateIndex != -1) codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); - } - caseStatement = (CaseStatement) statement; - caseIndex++; - caseStatement.typeSwitchIndex = typeSwitchIndex; - typeSwitchIndex += caseStatement.constantExpressions.length; - } else { - if (statement == this.defaultCase) { // statements[i] is a case or a default case - this.scope.enclosingCase = this.defaultCase; // record entering in a switch case block - if (this.preSwitchInitStateIndex != -1) { - codeStream.removeNotDefinitelyAssignedVariables(currentScope, this.preSwitchInitStateIndex); - } - } } - statementGenerateCode(currentScope, codeStream, statement); + statement.generateCode(this.scope, codeStream); + if ((this.switchBits & LabeledRules) != 0 && statement instanceof Block && statement.canCompleteNormally()) + codeStream.goto_(this.breakLabel); } } boolean needsThrowingDefault = false; @@ -862,12 +798,10 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { * All cases will return a value on the stack except the missing default case. * There is no returned value for the default case so we handle it with an exception thrown. */ - CompilerOptions compilerOptions = this.scope != null ? this.scope.compilerOptions() : null; - if (compilerOptions.complianceLevel >= ClassFileConstants.JDK19) { + if (this.scope.compilerOptions().complianceLevel >= ClassFileConstants.JDK19) { // since 19 we have MatchException for this - if (this.statements.length > 0 && this.statements[this.statements.length - 1].canCompleteNormally()) { + if (this.statements.length > 0 && this.statements[this.statements.length - 1].canCompleteNormally()) codeStream.goto_(this.breakLabel); // hop, skip and jump over match exception throw. - } defaultLabel.place(); codeStream.newJavaLangMatchException(); codeStream.dup(); @@ -890,9 +824,8 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { codeStream.addDefinitelyAssignedVariables(currentScope, this.mergedInitStateIndex); } generateCodeSwitchPatternEpilogue(codeStream); - if (this.scope != currentScope) { + if (this.scope != currentScope) codeStream.exitUserScope(this.scope); - } // place the trailing labels (for break and default case) this.breakLabel.place(); if (this.defaultCase == null && !needsThrowingDefault) { @@ -905,76 +838,50 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { if (this.scope != null) this.scope.enclosingCase = null; // no longer inside switch case block } } - private void transformConstants() { - if (this.nullCase == null) { - for (int i = 0,l = this.otherConstants.length; i < l; ++i) { - if (this.otherConstants[i].e == this.totalPattern) { - this.otherConstants[i].index = -1; - break; - } - } - } - for (int i = 0; i < this.constants.length; i++) { - this.constants[i] = this.otherConstants[i].index; - } - } + private void generateCodeSwitchPatternEpilogue(CodeStream codeStream) { - if (needPatternDispatchCopy()) { - codeStream.removeVariable(this.dispatchPatternCopy); - codeStream.removeVariable(this.restartIndexLocal); - } + if (needPatternDispatchCopy()) + codeStream.removeVariable(this.selector); } private void generateCodeSwitchPatternPrologue(BlockScope currentScope, CodeStream codeStream) { this.expression.generateCode(currentScope, codeStream, true); - if ((this.switchBits & NullCase) == 0 && !this.expression.resolvedType.isPrimitiveType()) { + if (!this.containsNull && !this.expression.resolvedType.isPrimitiveType()) { codeStream.dup(); codeStream.invokeJavaUtilObjectsrequireNonNull(); codeStream.pop(); } - codeStream.store(this.dispatchPatternCopy, false); - codeStream.addVariable(this.dispatchPatternCopy); + codeStream.store(this.selector, false); + codeStream.addVariable(this.selector); - codeStream.loadInt(0); // restartIndex - codeStream.store(this.restartIndexLocal, false); - codeStream.addVariable(this.restartIndexLocal); + int invokeDynamicNumber = codeStream.classFile.recordBootstrapMethod(this); + codeStream.load(this.selector); + codeStream.loadInt(0); // restartIndex this.switchPatternRestartTarget = new BranchLabel(codeStream); this.switchPatternRestartTarget.place(); - codeStream.load(this.dispatchPatternCopy); - codeStream.load(this.restartIndexLocal); - int invokeDynamicNumber = codeStream.classFile.recordBootstrapMethod(this); - if (this.expression.resolvedType.isEnum()) { + if (this.expression.resolvedType.isEnum()) generateEnumSwitchPatternPrologue(codeStream, invokeDynamicNumber); - } else { + else generateTypeSwitchPatternPrologue(codeStream, invokeDynamicNumber); - } + boolean hasQualifiedEnums = (this.switchBits & QualifiedEnum) != 0; - for (int i = 0; i < this.otherConstants.length; i++) { - ResolvedCase c = this.otherConstants[i]; - if (hasQualifiedEnums) { + for (int i = 0; i < this.labelExpressions.length; i++) { + LabelExpression c = this.labelExpressions[i]; + if (hasQualifiedEnums) c.index = i; - } - if (c.t.isPrimitiveType()) { - SingletonBootstrap descriptor = null; - if (c.isPattern()) { - descriptor = PRIMITIVE_CLASS__BOOTSTRAP; - } else if (c.t.id == TypeIds.T_boolean) { - descriptor = GET_STATIC_FINAL__BOOTSTRAP; - } - if (descriptor != null) { + if (c.type.isPrimitiveType()) { + SingletonBootstrap descriptor = c.isPattern() ? PRIMITIVE_CLASS__BOOTSTRAP : c.type.id == TypeIds.T_boolean ? GET_STATIC_FINAL__BOOTSTRAP : null; + if (descriptor != null) c.primitivesBootstrapIdx = codeStream.classFile.recordSingletonBootstrapMethod(descriptor); - } continue; } - if (!c.isQualifiedEnum()) - continue; - int classdescIdx = codeStream.classFile.recordBootstrapMethod(c.t); - invokeDynamicNumber = codeStream.classFile.recordBootstrapMethod(c); - c.enumDescIdx = invokeDynamicNumber; - c.classDescIdx = classdescIdx; + if (c.isQualifiedEnum()) { + c.enumDescIdx = codeStream.classFile.recordBootstrapMethod(c); + c.classDescIdx = codeStream.classFile.recordBootstrapMethod(c.type); + } } } private void generateTypeSwitchPatternPrologue(CodeStream codeStream, int invokeDynamicNumber) { @@ -1016,14 +923,7 @@ private void generateEnumSwitchPatternPrologue(CodeStream codeStream, int invoke callingParams.toCharArray(), TypeBinding.INT); } - protected void statementGenerateCode(BlockScope currentScope, CodeStream codeStream, Statement statement) { - statement.generateCode(this.scope, codeStream); - } - @Override - public void generateCode(BlockScope currentScope, CodeStream codeStream, boolean valueRequired) { - generateCode(currentScope, codeStream); // redirecting to statement part - } @Override public StringBuilder printStatement(int indent, StringBuilder output) { @@ -1032,33 +932,32 @@ public StringBuilder printStatement(int indent, StringBuilder output) { if (this.statements != null) { for (Statement statement : this.statements) { output.append('\n'); - if (statement instanceof CaseStatement) { + if (statement instanceof CaseStatement) statement.printStatement(indent, output); - } else { + else statement.printStatement(indent+2, output); - } } } output.append("\n"); //$NON-NLS-1$ return printIndent(indent, output).append('}'); } - private int getNConstants() { + private void preprocess() { int n = 0; for (final Statement statement : this.statements) { - if (statement instanceof CaseStatement) { - Expression[] exprs = ((CaseStatement) statement).peeledLabelExpressions(); + if (statement instanceof CaseStatement caseStatement) { + n++; int count = 0; - if (exprs != null) { - for (Expression e : exprs) { - if (e instanceof FakeDefaultLiteral) continue; - ++count; - } + for (Expression e : caseStatement.peeledLabelExpressions()) { + if (e instanceof FakeDefaultLiteral) + continue; + ++count; } - n += count; + this.nConstants += count; } } - return n; + this.labelExpressions = new LabelExpression[this.nConstants]; + this.cases = new CaseStatement[n]; } boolean isAllowedType(TypeBinding type) { @@ -1078,6 +977,57 @@ boolean isAllowedType(TypeBinding type) { } return false; } + + private boolean duplicateConstant(LabelExpression current, LabelExpression prior) { + if (current.expression instanceof Pattern || prior.expression instanceof Pattern) + return false; // apples and oranges + if (current.expression instanceof NullLiteral ^ prior.expression instanceof NullLiteral) // I actually got to use XOR! :) + return false; + if (current.constant.equals(prior.constant)) + return true; + if (current.type.id == TypeIds.T_boolean) + this.switchBits |= Exhaustive; // 2 different boolean constants => exhaustive :) + return false; + } + + void gatherLabelExpression(LabelExpression labelExpression) { + // domination check + if (labelExpression.expression instanceof Pattern pattern) { + if (this.defaultCase != null) { + this.scope.problemReporter().patternDominatedByAnother(pattern); + } else { + for (int i = 0; i < this.labelExpressionIndex; i++) { + if (this.labelExpressions[i].expression instanceof Pattern priorPattern && priorPattern.dominates(pattern)) { + this.scope.problemReporter().patternDominatedByAnother(pattern); + break; + } + } + } + } else { + if (labelExpression.expression instanceof NullLiteral) { + if (this.defaultCase != null) + this.scope.problemReporter().patternDominatedByAnother(labelExpression.expression); + } else { + TypeBinding boxedType = labelExpression.type.isBaseType() ? this.scope.environment().computeBoxingType(labelExpression.type) : labelExpression.type; + for (int i = 0; i < this.labelExpressionIndex; i++) { + if (this.labelExpressions[i].expression instanceof Pattern priorPattern && priorPattern.coversType(boxedType, this.scope)) { + this.scope.problemReporter().patternDominatedByAnother(labelExpression.expression); + break; + } + } + } + // duplicate constant check + for (int i = 0; i < this.labelExpressionIndex; i++) { + if (duplicateConstant(labelExpression, this.labelExpressions[i])) { + this.scope.problemReporter().duplicateCase(labelExpression.expression); + break; + } + } + } + this.labelExpressions[this.labelExpressionIndex++] = labelExpression; + } + + @Override public void resolve(BlockScope upperScope) { try { @@ -1115,130 +1065,36 @@ public void resolve(BlockScope upperScope) { } } + this.scope = new BlockScope(upperScope); if (expressionType != null) - reserveSecretVariablesSlots(upperScope); + reserveSecretVariablesSlots(); + else + this.switchBits |= InvalidSelector; + if (this.statements != null) { - if (this.scope == null) - this.scope = new BlockScope(upperScope); - int length; - // collection of cases is too big but we will only iterate until caseCount - this.cases = new CaseStatement[length = this.statements.length]; - this.nConstants = getNConstants(); - this.constants = new int[this.nConstants]; - this.otherConstants = new ResolvedCase[this.nConstants]; - this.constMapping = new int[this.nConstants]; - int counter = 0; - int caseCounter = 0; - Pattern[] patterns = new Pattern[this.nConstants]; - int[] caseIndex = new int[this.nConstants]; + preprocess(); // make a pass over the switch block and allocate vectors. LocalVariableBinding[] patternVariables = NO_VARIABLES; - boolean caseNullDefaultFound = false; - boolean defaultFound = false; - Pattern aTotalPattern = null; - for (int i = 0; i < length; i++) { - ResolvedCase[] constantsList; - final Statement statement = this.statements[i]; - if (statement instanceof CaseStatement caseStmt) { - caseNullDefaultFound |= isCaseStmtNullDefault(caseStmt); - defaultFound |= caseStmt.constantExpressions == Expression.NO_EXPRESSIONS; - constantsList = caseStmt.resolveCase(this.scope, expressionType, this); - patternVariables = statement.bindingsWhenTrue(); - for (ResolvedCase c : constantsList) { - if (c.e instanceof Pattern p && p.isTotalTypeNode && p.isUnguarded) - aTotalPattern = p; - Constant con = c.c; - if (con == Constant.NotAConstant) - continue; - this.otherConstants[counter] = c; - final int c1 = this.containsPatterns ? (c.intValue() == -1 ? -1 : counter) : c.intValue(); - this.constants[counter] = c1; - if (counter == 0 && defaultFound) { - if (c.isPattern() || isCaseStmtNullOnly(caseStmt)) - this.scope.problemReporter().patternDominatedByAnother(c.e); - } - for (int j = 0; j < counter; j++) { - IntPredicate check = idx -> { - ResolvedCase otherResolvedCase = this.otherConstants[idx]; - Constant c2 = otherResolvedCase.c; - if (con.typeID() == TypeIds.T_JavaLangString) { - return c2.stringValue().equals(con.stringValue()); - } else { - if (c2.typeID() == TypeIds.T_JavaLangString) - return false; - int id = c.t.id, otherId = otherResolvedCase.t.id; - if (id == TypeIds.T_null || otherId == TypeIds.T_null) - return id == otherId; // 'null' shares IntConstant(-1) - if (con.equals(c2)) - return true; - if (id == TypeIds.T_boolean) - this.switchBits |= Exhaustive; // 2 different boolean constants => exhaustive :) - return this.constants[idx] == c1; - } - }; - TypeBinding type = c.e.resolvedType; - if (!type.isValidBinding()) - continue; - if ((caseNullDefaultFound || defaultFound) && (c.isPattern() || isCaseStmtNullOnly(caseStmt))) { - this.scope.problemReporter().patternDominatedByAnother(c.e); - break; - } - Pattern p1 = patterns[j]; - if (p1 != null) { - if (c.isPattern()) { - if (p1.dominates((Pattern) c.e)) { - this.scope.problemReporter().patternDominatedByAnother(c.e); - } - } else { - if (type.id != TypeIds.T_null) { - if (type.isBaseType()) { - type = this.scope.environment().computeBoxingType(type); - } - if (p1.coversType(type, this.scope)) - this.scope.problemReporter().patternDominatedByAnother(c.e); - } - } - } else { - if (!c.isPattern() && check.test(j)) { - if (this.isNonTraditional) { - reportDuplicateCase(c.e, this.otherConstants[j].e, length); - } else { - reportDuplicateCase(caseStmt, this.cases[caseIndex[j]], length); - } - } - } - } - this.constMapping[counter] = counter; - caseIndex[counter] = caseCounter; - // Only the pattern expressions count for dominance check - if (c.e instanceof Pattern) { - patterns[counter] = (Pattern) c.e; - } - counter++; - } - caseCounter++; + for (final Statement statement : this.statements) { + if (statement instanceof CaseStatement caseStatement) { + caseStatement.swich = this; + caseStatement.resolve(this.scope); + patternVariables = caseStatement.bindingsWhenTrue(); } else { statement.resolveWithBindings(patternVariables, this.scope); patternVariables = LocalVariableBinding.merge(patternVariables, statement.bindingsWhenComplete()); } } - if (!defaultFound && aTotalPattern != null) { - this.totalPattern = aTotalPattern; - } if (expressionType != null && (expressionType.id == TypeIds.T_boolean || expressionType.id == TypeIds.T_JavaLangBoolean) - && defaultFound && isExhaustive()) { + && this.defaultCase != null && isExhaustive()) { upperScope.problemReporter().caseDefaultPlusTrueAndFalse(this); } - if (length != counter) { // resize constants array - System.arraycopy(this.otherConstants, 0, this.otherConstants = new ResolvedCase[counter], 0, counter); - System.arraycopy(this.constants, 0, this.constants = new int[counter], 0, counter); - System.arraycopy(this.constMapping, 0, this.constMapping = new int[counter], 0, counter); - } + if (this.labelExpressions.length != this.labelExpressionIndex) + System.arraycopy(this.labelExpressions, 0, this.labelExpressions = new LabelExpression[this.labelExpressionIndex], 0, this.labelExpressionIndex); } else { - if ((this.bits & UndocumentedEmptyBlock) != 0) { + if ((this.bits & UndocumentedEmptyBlock) != 0) upperScope.problemReporter().undocumentedEmptyBlock(this.blockStart, this.sourceEnd); - } } if (expressionType != null) { @@ -1247,13 +1103,14 @@ && defaultFound && isExhaustive()) { if (!JavaFeature.PATTERN_MATCHING_IN_SWITCH.isSupported(compilerOptions) || (expressionType.isBaseType() && expressionType.id != T_null && expressionType.id != T_void)) { if (!this.isPrimitiveSwitch) { // when isPrimitiveSwitch is set it is approved above upperScope.problemReporter().incorrectSwitchType(this.expression, expressionType); + this.switchBits |= InvalidSelector; expressionType = null; // fault-tolerance: ignore type mismatch from constants from hereon } } } else this.expression.computeConversion(upperScope, TypeBinding.INT, expressionType); } - releaseUnusedSecretVariables(upperScope); + releaseUnusedSecretVariables(); complainIfNotExhaustiveSwitch(upperScope, expressionType, compilerOptions); } @@ -1270,10 +1127,14 @@ private void complainIfNotExhaustiveSwitch(BlockScope upperScope, TypeBinding se if (this.defaultCase != null && !compilerOptions.reportMissingEnumCaseDespiteDefault) return; - int constantCount = this.otherConstants == null ? 0 : this.otherConstants.length; - if (!((this.switchBits & TotalPattern) != 0) && + int casesCount = this.caseCount; + if (this.defaultCase != null && this.defaultCase.constantExpressions == NO_EXPRESSIONS) + casesCount--; // discount the default + + int constantCount = this.labelExpressions == null ? 0 : this.labelExpressions.length; + if (!(this.totalPattern != null) && ((this.containsPatterns || this.containsNull) || - (constantCount >= this.caseCount && + (constantCount >= casesCount && constantCount != ((ReferenceBinding)selectorType).enumConstantCount()))) { Set unenumeratedConstants = unenumeratedConstants((ReferenceBinding) selectorType, constantCount); if (unenumeratedConstants.size() != 0) { @@ -1335,8 +1196,8 @@ private Set unenumeratedConstants(ReferenceBinding enumType, int c continue; } for (int j = 0; j < constantCount; j++) { - if (TypeBinding.equalsEquals(this.otherConstants[j].e.resolvedType, enumType)) { - if (this.otherConstants[j].e instanceof NameReference reference) { + if (TypeBinding.equalsEquals(this.labelExpressions[j].expression.resolvedType, enumType)) { + if (this.labelExpressions[j].expression instanceof NameReference reference) { FieldBinding field = reference.fieldBinding(); int intValue = field.original().id + 1; if ((enumConstant.id + 1) == intValue) { // zero should not be returned see bug 141810 @@ -1349,17 +1210,6 @@ private Set unenumeratedConstants(ReferenceBinding enumType, int c } return unenumerated; } - private boolean isCaseStmtNullDefault(CaseStatement caseStmt) { - return caseStmt != null - && caseStmt.constantExpressions.length == 2 - && caseStmt.constantExpressions[0] instanceof NullLiteral - && caseStmt.constantExpressions[1] instanceof FakeDefaultLiteral; - } - private boolean isCaseStmtNullOnly(CaseStatement caseStmt) { - return caseStmt != null - && caseStmt.constantExpressions.length == 1 - && caseStmt.constantExpressions[0] instanceof NullLiteral; - } private boolean isExhaustive() { return (this.switchBits & SwitchStatement.Exhaustive) != 0; } @@ -1430,7 +1280,7 @@ private boolean caseElementsCoverSealedType(ReferenceBinding sealedType, List unenumeratedConstants = unenumeratedConstants(next, constantCount); if (unenumeratedConstants.size() == 0) { iterator.remove(); @@ -1448,11 +1298,9 @@ private boolean caseElementsCoverSealedType(ReferenceBinding sealedType, List threadCache; public DiagnosticListener diagnosticListener; - public static final RuntimeException REACHED_DEAD_CODE = new RuntimeException() { private static final long serialVersionUID = 1L; }; + public static final RuntimeException UNEXPECTED_CONTROL_FLOW = new RuntimeException() { private static final long serialVersionUID = 1L; }; public EclipseCompiler() { this.threadCache = new WeakHashMap<>(); diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ConstantTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ConstantTest.java index 76c696bb4f2..253a0eb3ee9 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ConstantTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/ConstantTest.java @@ -1578,12 +1578,7 @@ public void testBug566332_04() { + "} ", }, "----------\n" + - "1. WARNING in X.java (at line 4)\n" + - " String caseStr = true ? \"abc\" : \"def\";\n" + - " ^^^^^\n" + - "Dead code\n" + - "----------\n" + - "2. ERROR in X.java (at line 6)\n" + + "1. ERROR in X.java (at line 6)\n" + " case caseStr: System.out.println(\"Pass\");\n" + " ^^^^^^^\n" + "case expressions must be constant expressions\n" + diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java index 28f71bd72fe..df1b0361757 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/EnumTest.java @@ -435,14 +435,9 @@ public void test010() { "}" }, "----------\n" + - "1. ERROR in X.java (at line 12)\n" + + "1. ERROR in X.java (at line 14)\n" + " case BLEU :\n" + - " ^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 14)\n" + - " case BLEU :\n" + - " ^^^^^^^^^\n" + + " ^^^^\n" + "Duplicate case\n" + "----------\n"); } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java index eca3965acaf..bf10670c081 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/LocalEnumTest.java @@ -581,14 +581,9 @@ public void test010() { " ^^^^\n" + "The parameter args is hiding another local variable defined in an enclosing scope\n" + "----------\n" + - "2. ERROR in X.java (at line 14)\n" + + "2. ERROR in X.java (at line 16)\n" + " case BLEU :\n" + - " ^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "3. ERROR in X.java (at line 16)\n" + - " case BLEU :\n" + - " ^^^^^^^^^\n" + + " ^^^^\n" + "Duplicate case\n" + "----------\n"); } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java index a50ab120030..766b7322421 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/NullAnnotationTest.java @@ -11425,4 +11425,89 @@ public void testBug565246() { runner.runConformTest(); } } +// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3319 +// [Enhanced Switch][Null] Inconsistent nullness propagation +public void _testIssue3319() { + if (this.complianceLevel < ClassFileConstants.JDK21) + return; + Map customOptions = getCompilerOptions(); + customOptions.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, JavaCore.IGNORE); // has no effect + runNegativeTestWithLibs( + new String[] { + "X.java", + """ + import org.eclipse.jdt.annotation.NonNull; + + public class X { + static @NonNull Object foo(Object o) { + switch (o) { + case String s -> { + if (o == null) { + System.out.println("o cannot be null at all!"); + } + System.out.println(); + } + default -> { + if (o == null) { + System.out.println("o cannot be null at all!"); + } + System.out.println(); + } + } + return new Object(); + } + + static @NonNull Object foo(X o) { + switch (o) { + case X s -> { + if (o == null) { + System.out.println("o cannot be null at all!"); + } + System.out.println(s); + } + } + return new Object(); + } + } + """, + }, + customOptions, + "----------\n" + + "1. ERROR in X.java (at line 7)\n" + + " if (o == null) {\n" + + " ^\n" + + "Null comparison always yields false: The variable o cannot be null at this location\n" + + "----------\n" + + "2. WARNING in X.java (at line 7)\n" + + " if (o == null) {\n" + + " System.out.println(\"o cannot be null at all!\");\n" + + " }\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Dead code\n" + + "----------\n" + + "3. ERROR in X.java (at line 13)\n" + + " if (o == null) {\n" + + " ^\n" + + "Null comparison always yields false: The variable o cannot be null at this location\n" + + "----------\n" + + "4. WARNING in X.java (at line 13)\n" + + " if (o == null) {\n" + + " System.out.println(\"o cannot be null at all!\");\n" + + " }\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Dead code\n" + + "----------\n" + + "5. ERROR in X.java (at line 25)\n" + + " if (o == null) {\n" + + " ^\n" + + "Null comparison always yields false: The variable o cannot be null at this location\n" + + "----------\n" + + "6. WARNING in X.java (at line 25)\n" + + " if (o == null) {\n" + + " System.out.println(\"o cannot be null at all!\");\n" + + " }\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Dead code\n" + + "----------\n"); +} } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTest.java index 0d74085de13..7c782c38245 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTest.java @@ -7091,8 +7091,8 @@ public static void main(String[] args) { "2"); } // test from spec - public void _testSpec001() { - runConformTest(new String[] { + public void testSpec001() { + runNegativeTest(new String[] { "X.java", """ public class X { @@ -7112,10 +7112,15 @@ public static void main(String[] args) { } """ }, - "100"); + "----------\n" + + "1. ERROR in X.java (at line 8)\r\n" + + " default -> -1;\r\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); } - public void _testSpec002() { - runConformTest(new String[] { + public void testSpec002() { + runNegativeTest(new String[] { "X.java", """ public class X { @@ -7136,9 +7141,14 @@ public static void main(String[] args) { } """ }, - "100"); + "----------\n" + + "1. ERROR in X.java (at line 9)\n" + + " default -> -1;\n" + + " ^^^^^^^\n" + + "Switch case cannot have both unconditional pattern and default label\n" + + "----------\n"); } - public void _testSpec003() { + public void testSpec003() { runConformTest(new String[] { "X.java", """ @@ -7166,7 +7176,7 @@ public static void main(String[] args) { }, "JsonNumber[d=30.0]"); } - public void _testSpec004() { + public void testSpec004() { runConformTest(new String[] { "X.java", """ @@ -7201,7 +7211,7 @@ public static void main(String[] args) { }, "30"); } - public void _testSpec005() { + public void testSpec005() { runConformTest(new String[] { "X.java", """ @@ -7235,7 +7245,7 @@ public static void main(String[] args) { }, "double:30.0"); } - public void _testSpec006() { + public void testSpec006() { runConformTest(new String[] { "X.java", """ @@ -7285,4 +7295,28 @@ public void _testSpec00X() { "----------\n"); } + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3336 + // [Enhanced Switch][Primitive Patterns] Bogus error: Case constants in a switch on 'Long' must have type 'long' + public void testIssue3336() { + runConformTest(new String[] { + "X.java", + """ + public interface X { + + public static void main(String[] args) { + Long lng = Long.valueOf(42); + + switch (lng) { + case -1l -> System.out.println("-1L"); + case null -> System.out.println("Null"); + default -> System.out.println("Default"); + } + } + + } + """ + }, + "Default"); + } + } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java index 505e3970e95..3d0f89f1efc 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/PrimitiveInPatternsTestSH.java @@ -1949,12 +1949,7 @@ public static void main(String... args) { }, """ ---------- - 1. ERROR in XBoolean.java (at line 4) - case true -> 1; - ^^^^ - Duplicate case - ---------- - 2. ERROR in XBoolean.java (at line 5) + 1. ERROR in XBoolean.java (at line 5) case true -> 2; ^^^^ Duplicate case @@ -2427,212 +2422,45 @@ public static void main(String[] args) { "2.0"); } - // test from spec - public void _testSpec001() { - runConformTest(new String[] { - "X.java", - """ - public class X { - public int getStatus() { - return 100; - } - public static int foo(X x) { - return switch (x.getStatus()) { - case int i -> i; - default -> -1; - }; - } - public static void main(String[] args) { - X x = new X(); - System.out.println(X.foo(x)); - } - } - """ - }, - "100"); - } - public void _testSpec002() { + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3265 + // [Primitive Patterns] Wrong duplicate case error + public void testIssue3265() { runConformTest(new String[] { - "X.java", + "X.java", """ - public class X { - public int getStatus() { - return 100; - } - public static int foo(X x) { - return switch (x.getStatus()) { - case int i when i > 10 -> i * i; - case int i -> i; - default -> -1; + public class X { + public static String switchfloatMoreCases(float f) { + return switch (f) { + case 1.0f -> "1.0"; + case 1.5f -> "1.5"; + default -> String.valueOf(f); }; - } - public static void main(String[] args) { - X x = new X(); - System.out.println(X.foo(x)); - } - } - """ - }, - "100"); - } - public void _testSpec003() { - runConformTest(new String[] { - "X.java", - """ - import java.util.Map; - - sealed interface JsonValue {} - record JsonString(String s) implements JsonValue { } - record JsonNumber(double d) implements JsonValue { } - record JsonObject(Map map) implements JsonValue { } - - - public class X { - - public static void foo() { - var json = new JsonObject(Map.of("name", new JsonString("John"), - "age", new JsonNumber(30))); - JsonValue v = json.map().get("age"); - System.out.println(v); - } - public static void main(String[] args) { - X.foo(); - } - } - """ - }, - "JsonNumber[d=30.0]"); - } - public void _testSpec004() { - runConformTest(new String[] { - "X.java", - """ - import java.util.Map; - - sealed interface JsonValue {} - record JsonString(String s) implements JsonValue { } - record JsonNumber(double d) implements JsonValue { } - record JsonObject(Map map) implements JsonValue { } - - - public class X { - - public static JsonObject foo() { - var json = new JsonObject(Map.of("name", new JsonString("John"), - "age", new JsonNumber(30))); - return json; - } - public static void bar(Object json) { - if (json instanceof JsonObject(var map) - && map.get("name") instanceof JsonString(String n) - && map.get("age") instanceof JsonNumber(double a)) { - int age = (int)a; // unavoidable (and potentially lossy!) cast - System.out.println(age); - } - } - public static void main(String[] args) { - X.bar(X.foo()); - } - } - """ - }, - "30"); - } - public void _testSpec005() { - runConformTest(new String[] { - "X.java", - """ - import java.util.HashMap; - import java.util.Map; - - sealed interface I {} - record ZNumber(double d) implements I { } - record ZObject(Map map) implements I { } - - - public class X { - - public static ZObject foo() { - Map myMap = new HashMap<>(); - myMap.put("age", new ZNumber(30)); - return new ZObject(myMap); - } - public static void bar(Object json) { - if (json instanceof ZObject(var map)) { - if (map.get("age") instanceof ZNumber(double d)) { - System.out.println("double:"+d); - } - } - } - public static void main(String[] args) { - X.bar(X.foo()); - } } - """ - }, - "double:30.0"); - } - public void _testSpec006() { - runConformTest(new String[] { - "X.java", - """ - import java.util.HashMap; - import java.util.Map; - - sealed interface I {} - record ZNumber(double d) implements I { } - record ZObject(Map map) implements I { } - - - public class X { - public static ZObject foo() { - Map myMap = new HashMap<>(); - myMap.put("age", new ZNumber(30)); - return new ZObject(myMap); - } - public static void bar(Object json) { - if (json instanceof ZObject(var map)) { - if (map.get("age") instanceof ZNumber(int i)) { - System.out.println("int:"+i); - } else if (map.get("age") instanceof ZNumber(double d)) { - System.out.println("double:"+d); - } - } - } - public static void main(String[] args) { - X.bar(X.foo()); - } + public static void main(String... args) { + System.out.print(switchfloatMoreCases(1.0f)); + System.out.print("|"); + System.out.print(switchfloatMoreCases(1.5f)); + System.out.print("|"); + System.out.print(switchfloatMoreCases(1.6f)); + System.out.print("|"); } - """ - }, - "int:30"); - } - public void _testSpec00X() { - runNegativeTest(new String[] { - "X.java", - """ - """ - }, - "----------\n" + - "2. ERROR in X.java (at line 16)\n" + - " Zork();\n" + - " ^^^^\n" + - "The method Zork() is undefined for the type X\n" + - "----------\n"); + } + """}, + "1.0|1.5|1.6|"); } // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3265 // [Primitive Patterns] Wrong duplicate case error - public void _testIssue3265() { - runConformTest(new String[] { + public void testIssue3265_2() { + runNegativeTest(new String[] { "X.java", """ public class X { public static String switchfloatMoreCases(float f) { return switch (f) { case 1.0f -> "1.0"; - case 1.5f -> "1.5"; + case 0.5f + 0.5f -> "1.0"; default -> String.valueOf(f); }; } @@ -2647,6 +2475,43 @@ public static void main(String... args) { } } """}, - "1.0|1.5|1.6|"); + "----------\n" + + "1. ERROR in X.java (at line 5)\n" + + " case 0.5f + 0.5f -> \"1.0\";\n" + + " ^^^^^^^^^^^\n" + + "Duplicate case\n" + + "----------\n"); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3337 + // [Enhanced Switch][Primitive Patterns] ECJ tolerates default case in boolean switch with both true and false cases. + public void testIssue3337() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + + public static void main(String[] args) { + Boolean b = true; + switch (b) { + case true -> System.out.println(1); + case false -> System.out.println(0); + case null, default -> System.out.println("Error"); + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 5)\r\n" + + " switch (b) {\n" + + " case true -> System.out.println(1);\n" + + " case false -> System.out.println(0);\n" + + " case null, default -> System.out.println(\"Error\");\n" + + " }\r\n" + + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + "Switch cannot have both boolean values and a default label\n" + + "----------\n"); + } + } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java index e746f2aad99..2f9c460523d 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchExpressionsYieldTest.java @@ -631,16 +631,11 @@ public void testBug544073_019() { String expectedProblemLog = "----------\n" + - "1. ERROR in X.java (at line 4)\n" + - " case SATURDAY, SUNDAY: \n" + - " ^^^^^^^^^^^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 7)\n" + - " case SUNDAY : System.out.println(Day.SUNDAY);\n" + - " ^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n"; + "1. ERROR in X.java (at line 7)\n" + + " case SUNDAY : System.out.println(Day.SUNDAY);\n" + + " ^^^^^^\n" + + "Duplicate case\n" + + "----------\n"; this.runNegativeTest( testFiles, expectedProblemLog); @@ -673,19 +668,14 @@ public void testBug544073_020() { " ^^^\n" + "The enum constant MONDAY needs a corresponding case label in this enum switch on Day\n" + "----------\n" + - "2. ERROR in X.java (at line 4)\n" + - " case SATURDAY, SUNDAY: \n" + - " ^^^^^^^^^^^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "3. ERROR in X.java (at line 7)\n" + + "2. ERROR in X.java (at line 7)\n" + " case SUNDAY, SATURDAY : \n" + - " ^^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^\n" + "Duplicate case\n" + "----------\n" + - "4. ERROR in X.java (at line 7)\n" + + "3. ERROR in X.java (at line 7)\n" + " case SUNDAY, SATURDAY : \n" + - " ^^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^^^\n" + "Duplicate case\n" + "----------\n"; this.runNegativeTest( @@ -1025,14 +1015,9 @@ public void testBug544073_030() { }; String expectedProblemLog = "----------\n" + - "1. ERROR in X.java (at line 6)\n" + - " case 1, 3: \n" + - " ^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 8)\n" + + "1. ERROR in X.java (at line 8)\n" + " case 3, 4: \n" + - " ^^^^^^^^^\n" + + " ^\n" + "Duplicate case\n" + "----------\n"; this.runNegativeTest( @@ -1060,14 +1045,9 @@ public void testBug544073_031() { }; String expectedProblemLog = "----------\n" + - "1. ERROR in X.java (at line 6)\n" + - " case \"a\", \"b\": \n" + - " ^^^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 8)\n" + + "1. ERROR in X.java (at line 8)\n" + " case \"b\", \"c\": \n" + - " ^^^^^^^^^^^^^\n" + + " ^^^\n" + "Duplicate case\n" + "----------\n"; this.runNegativeTest( @@ -2204,7 +2184,7 @@ public void testBug544073_078() { "----------\n" + "1. ERROR in X.java (at line 4)\n" + " case SATURDAY, SUNDAY, SUNDAY:\n" + - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^\n" + "Duplicate case\n" + "----------\n"); } @@ -2229,19 +2209,14 @@ public void testBug544073_079() { "}", }, "----------\n" + - "1. ERROR in X.java (at line 4)\n" + - " case SATURDAY, SUNDAY, MONDAY:\n" + - " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 6)\n" + + "1. ERROR in X.java (at line 6)\n" + " case MONDAY, SUNDAY:\n" + - " ^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^\n" + "Duplicate case\n" + "----------\n" + - "3. ERROR in X.java (at line 6)\n" + + "2. ERROR in X.java (at line 6)\n" + " case MONDAY, SUNDAY:\n" + - " ^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^\n" + "Duplicate case\n" + "----------\n"); } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java index 5c5d73121f6..e19cf5b599a 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest.java @@ -47,9 +47,6 @@ public SwitchPatternTest(String testName){ // Enables the tests to run individually protected Map getCompilerOptions() { Map defaultOptions = super.getCompilerOptions(); - defaultOptions.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_21); - defaultOptions.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_21); - defaultOptions.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_21); defaultOptions.put(CompilerOptions.OPTION_EnablePreviews, CompilerOptions.DISABLED); defaultOptions.put(CompilerOptions.OPTION_ReportPreviewFeatures, CompilerOptions.IGNORE); defaultOptions.put(CompilerOptions.OPTION_Store_Annotations, CompilerOptions.ENABLED); @@ -259,25 +256,15 @@ public void testBug573516_005() { "----------\n" + "2. ERROR in X.java (at line 4)\n" + " case Integer t, String s, X x : System.out.println(\"Integer, String or X\");\n" + - " ^^^^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "3. ERROR in X.java (at line 4)\n" + - " case Integer t, String s, X x : System.out.println(\"Integer, String or X\");\n" + " ^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "4. ERROR in X.java (at line 4)\n" + - " case Integer t, String s, X x : System.out.println(\"Integer, String or X\");\n" + - " ^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "5. ERROR in X.java (at line 4)\n" + + "3. ERROR in X.java (at line 4)\n" + " case Integer t, String s, X x : System.out.println(\"Integer, String or X\");\n" + " ^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "6. ERROR in X.java (at line 10)\n" + + "4. ERROR in X.java (at line 10)\n" + " Zork();\n" + " ^^^^\n" + "The method Zork() is undefined for the type X\n" + @@ -309,35 +296,25 @@ public void testBug573516_006() { "----------\n" + "2. ERROR in X.java (at line 4)\n" + " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + - " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "3. ERROR in X.java (at line 4)\n" + - " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + " ^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "4. ERROR in X.java (at line 4)\n" + + "3. ERROR in X.java (at line 4)\n" + " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + " ^^^^^^^^^^^^^^^^^\n" + "Syntax error on token(s), misplaced construct(s)\n" + "----------\n" + - "5. ERROR in X.java (at line 4)\n" + - " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + - " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "6. ERROR in X.java (at line 4)\n" + + "4. ERROR in X.java (at line 4)\n" + " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + " ^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "7. ERROR in X.java (at line 4)\n" + + "5. ERROR in X.java (at line 4)\n" + " case Integer t, String s when s.length > 0, X x when x.hashCode() > 10 : System.out.println(\"Integer, String or X\");\n" + " ^\n" + "x cannot be resolved\n" + "----------\n" + - "8. ERROR in X.java (at line 10)\n" + + "6. ERROR in X.java (at line 10)\n" + " Zork();\n" + " ^^^^\n" + "The method Zork() is undefined for the type X\n" + @@ -997,10 +974,10 @@ public void testBug574549_03() { "}", }, "----------\n" + - "1. ERROR in X.java (at line 11)\n" + + "1. ERROR in X.java (at line 13)\n" + " case null, default:\n" + " ^^^^\n" + - "Duplicate case\n" + + "This case label is dominated by one of the preceding case labels\n" + "----------\n" + "2. ERROR in X.java (at line 13)\n" + " case null, default:\n" + @@ -1919,30 +1896,20 @@ public void testBug574564_002() { "----------\n" + "3. ERROR in X.java (at line 7)\n" + " case var i, var j, var k -> System.out.println(0);\n" + - " ^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "4. ERROR in X.java (at line 7)\n" + - " case var i, var j, var k -> System.out.println(0);\n" + " ^^^\n" + "'var' is not allowed here\n" + "----------\n" + - "5. ERROR in X.java (at line 7)\n" + + "4. ERROR in X.java (at line 7)\n" + " case var i, var j, var k -> System.out.println(0);\n" + " ^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "6. ERROR in X.java (at line 7)\n" + - " case var i, var j, var k -> System.out.println(0);\n" + - " ^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "7. ERROR in X.java (at line 7)\n" + + "5. ERROR in X.java (at line 7)\n" + " case var i, var j, var k -> System.out.println(0);\n" + " ^^^\n" + "'var' is not allowed here\n" + "----------\n" + - "8. ERROR in X.java (at line 7)\n" + + "6. ERROR in X.java (at line 7)\n" + " case var i, var j, var k -> System.out.println(0);\n" + " ^\n" + "Named pattern variables are not allowed here\n" + @@ -2198,8 +2165,12 @@ public void testBug574564_009() { " case String s, default, Integer i -> System.out.println(0);\n" + " ^^^^^^^^^\n" + "Cannot mix pattern with other case labels\n" + - "----------\n" -); + "----------\n" + + "4. ERROR in X.java (at line 4)\n" + + " case String s, default, Integer i -> System.out.println(0);\n" + + " ^^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n"); } public void testBug574564_010() { Map options = getCompilerOptions(); @@ -2230,6 +2201,11 @@ public void testBug574564_010() { " case String s, default, Integer i -> System.out.println(0);\n" + " ^^^^^^^^^\n" + "Cannot mix pattern with other case labels\n" + + "----------\n" + + "4. ERROR in X.java (at line 4)\n" + + " case String s, default, Integer i -> System.out.println(0);\n" + + " ^^^^^^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + "----------\n", null, true, @@ -2281,11 +2257,6 @@ public void testBug574563_001() { "----------\n" + "1. ERROR in X.java (at line 5)\n" + " case null, null -> System.out.println(o);\n" + - " ^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 5)\n" + - " case null, null -> System.out.println(o);\n" + " ^^^^\n" + "Duplicate case\n" + "----------\n"); @@ -3249,6 +3220,11 @@ public void testBug575356_03() { " case default, null -> System.out.println(\"hello\");\n" + " ^^^^\n" + "A null case label has to be either the only expression in a case label or the first expression followed only by a default\n" + + "----------\n" + + "3. ERROR in X.java (at line 4)\n" + + " case default, null -> System.out.println(\"hello\");\n" + + " ^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + "----------\n"); } public void testBug575356_04() { @@ -4030,38 +4006,38 @@ public void testBug575686_1() { "----------\n" + "2. ERROR in X.java (at line 4)\n" + " case Integer i1, String s1 ->\n" + - " ^^^^^^^^^\n" + - "Cannot mix pattern with other case labels\n" + - "----------\n" + - "3. ERROR in X.java (at line 4)\n" + - " case Integer i1, String s1 ->\n" + " ^^\n" + "Named pattern variables are not allowed here\n" + "----------\n" + - "4. ERROR in X.java (at line 5)\n" + + "3. ERROR in X.java (at line 5)\n" + " System.out.print(s1);\n" + " ^^\n" + "s1 cannot be resolved to a variable\n" + "----------\n" + - "5. ERROR in X.java (at line 7)\n" + + "4. ERROR in X.java (at line 7)\n" + " case Number n, null ->\n" + " ^^^^^^^^\n" + "This case label is dominated by one of the preceding case labels\n" + "----------\n" + - "6. ERROR in X.java (at line 7)\n" + + "5. ERROR in X.java (at line 7)\n" + " case Number n, null ->\n" + " ^^^^\n" + "Cannot mix pattern with other case labels\n" + "----------\n" + - "7. ERROR in X.java (at line 7)\n" + + "6. ERROR in X.java (at line 7)\n" + " case Number n, null ->\n" + " ^^^^\n" + "A null case label has to be either the only expression in a case label or the first expression followed only by a default\n" + "----------\n" + - "8. ERROR in X.java (at line 7)\n" + + "7. ERROR in X.java (at line 7)\n" + " case Number n, null ->\n" + " ^^^^\n" + - "Duplicate case\n" + + "This case label is dominated by one of the preceding case labels\n" + + "----------\n" + + "8. ERROR in X.java (at line 9)\n" + + " case null, Class c ->\n" + + " ^^^^\n" + + "This case label is dominated by one of the preceding case labels\n" + "----------\n" + "9. ERROR in X.java (at line 9)\n" + " case null, Class c ->\n" + @@ -7415,28 +7391,26 @@ record S() {} String expectedOutput = " // Method descriptor #22 (Ljava/lang/Object;)V\n" + - " // Stack: 2, Locals: 3\n" + + " // Stack: 2, Locals: 2\n" + " public static void foo(java.lang.Object o);\n" + " 0 aload_0 [o]\n" + " 1 dup\n" + " 2 invokestatic java.util.Objects.requireNonNull(java.lang.Object) : java.lang.Object [30]\n" + " 5 pop\n" + " 6 astore_1\n" + - " 7 iconst_0\n" + - " 8 istore_2\n" + - " 9 aload_1\n" + - " 10 iload_2\n" + - " 11 invokedynamic 0 typeSwitch(java.lang.Object, int) : int [36]\n" + - " 16 tableswitch default: 56\n" + - " case 0: 40\n" + - " case 1: 48\n" + - " 40 getstatic java.lang.System.out : java.io.PrintStream [40]\n" + - " 43 ldc [46]\n" + - " 45 invokevirtual java.io.PrintStream.println(java.lang.String) : void [48]\n" + - " 48 getstatic java.lang.System.out : java.io.PrintStream [40]\n" + - " 51 ldc [54]\n" + - " 53 invokevirtual java.io.PrintStream.println(java.lang.String) : void [48]\n" + - " 56 return\n"; + " 7 aload_1\n" + + " 8 iconst_0\n" + + " 9 invokedynamic 0 typeSwitch(java.lang.Object, int) : int [36]\n" + + " 14 tableswitch default: 52\n" + + " case 0: 36\n" + + " case 1: 44\n" + + " 36 getstatic java.lang.System.out : java.io.PrintStream [40]\n" + + " 39 ldc [46]\n" + + " 41 invokevirtual java.io.PrintStream.println(java.lang.String) : void [48]\n" + + " 44 getstatic java.lang.System.out : java.io.PrintStream [40]\n" + + " 47 ldc [54]\n" + + " 49 invokevirtual java.io.PrintStream.println(java.lang.String) : void [48]\n" + + " 52 return\n"; SwitchPatternTest.verifyClassFile(expectedOutput, "X.class", ClassFileBytesDisassembler.SYSTEM); } @@ -9294,4 +9268,243 @@ final class AB implements I2 {} "Syntax error on token \"I2\", permits expected after this token\n" + "----------\n"); } + + public void testNoFallThrough() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String[] args) { + switch ("Hello") { + case "Hello" -> { + System.out.println("Hello Block!"); + } + case "World" -> { + System.out.println("World Block!"); + } + default -> { + System.out.println("Default Block"); + } + } + } + } + """ + }, + "Hello Block!"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3169 + // [21][Enhanced Switch] Bogus error: "Cannot mix pattern with other case labels + public void testIssue3169() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + static void foo(Object o) { + switch (o) { + case Character c, Integer i: // Compile-time error + break; + default: + break; + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " case Character c, Integer i: // Compile-time error\n" + + " ^\n" + + "Named pattern variables are not allowed here\n" + + "----------\n" + + "2. ERROR in X.java (at line 4)\n" + + " case Character c, Integer i: // Compile-time error\n" + + " ^\n" + + "Named pattern variables are not allowed here\n" + + "----------\n"); + } + + public void testEnumLocalCase() { + this.runNegativeTest( + new String[] { + "X.java", + "public enum X {\n" + + " A, B, C;\n" + + "}\n" + + "\n" + + "class A {\n" + + " private void foo(X x) {\n" + + " final X v = null;\n" + + " switch (x) {\n" + + " case v:\n" + + " }\n" + + " }\n" + + "}\n", + }, + "----------\n" + + "1. ERROR in X.java (at line 9)\n" + + " case v:\n" + + " ^\n" + + "v cannot be resolved or is not a field\n" + + "----------\n"); + } + + public void testEnumLocalCase_2() { + this.runNegativeTest( + new String[] { + "X.java", + """ + public sealed interface X { + enum E implements X { + E1, E2; + } + public static void main(String[] args) { + E e = null; + switch ((X) null) { + case e -> System.out.println(); + case E.E2 -> System.out.println(); + default -> System.out.println(); + } + } + } + """, + }, + "----------\n" + + "1. ERROR in X.java (at line 8)\n" + + " case e -> System.out.println();\n" + + " ^\n" + + "case expressions must be constant expressions\n" + + "----------\n"); + } + + public void testEnumLocalCase_3() { + this.runNegativeTest( + new String[] { + "X.java", + """ + public sealed interface X { + enum E implements X { + E1, E2; + } + public static final E e = E.E1; + public static void main(String[] args) { + switch ((X) null) { + case e -> System.out.println(); + case E.E2 -> System.out.println(); + default -> System.out.println(); + } + } + } + """, + }, + "----------\n" + + "1. ERROR in X.java (at line 8)\n" + + " case e -> System.out.println();\n" + + " ^\n" + + "The field X.e cannot be referenced from an enum case label; only enum constants can be used in enum switch\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3334 + // [Enhanced Switch] Bogus duplicate case error from ECJ + public void testIssue3334() { + runConformTest( + new String[] { + "X.java", + """ + public sealed interface X { + public static void main(String[] args) { + bar(E1.ONE); + bar(E1.TWO); + bar(E2.ONE); + bar(E2.TWO); + } + public static void bar(X x) { + switch (x) { + case E1.ONE: + System.out.println("E1.ONE"); + case E1.TWO: + System.out.println("E1.TWO"); + case E2.ONE: + System.out.println("E2.ONE"); + case E2.TWO: + System.out.println("E2.TWO"); + } + } + } + enum E1 implements X { ONE, TWO} + enum E2 implements X { ONE, TWO} + """ + }, + "E1.ONE\n" + + "E1.TWO\n" + + "E2.ONE\n" + + "E2.TWO\n" + + "E1.TWO\n" + + "E2.ONE\n" + + "E2.TWO\n" + + "E2.ONE\n" + + "E2.TWO\n" + + "E2.TWO"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3335 + // [Enhanced Switch][Record Patterns] ECJ compiles non-exhaustive switch resulting in MatchException being thrown at runtime + public void testIssue3335() { + runNegativeTest( + new String[] { + "X.java", + """ + public sealed interface X { + + record R(X x) {} + + final class C1 implements X {} + final class C2 implements X {} + + public static void main(String[] args) { + bar(new R(new C1())); + } + public static void bar(R r) { + switch (r) { + case R(C1 c1) when c1 == null -> System.out.println(); + case R(C2 c1) -> System.out.println(); + } + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 12)\n" + + " switch (r) {\n" + + " ^\n" + + "An enhanced switch statement should be exhaustive; a default label expected\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3339 + // [Enhanced Switch][Regression] Incorrect duplicate case error since https://github.com/eclipse-jdt/eclipse.jdt.core/pull/3264 + public void testIssue3339() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + public static void main(String[] args) { + Integer i = 42; + switch (i) { + case 2 -> System.out.println(2); + case Integer ii when ii == 13 -> System.out.println("13"); + case 13 -> System.out.println(13); + case 14 -> System.out.println(14); + default -> System.out.println("Default"); + } + } + } + """ + }, + "Default"); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java index 3dade7af915..f71387f3177 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchPatternTest22.java @@ -996,4 +996,93 @@ record None() implements Maybe{} }, ""); } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3318 + // [Enhanced Switch] ECJ tolerates fall through to default from a case with pattern label while javac rejects it. + public void testIssue3318() { + runNegativeTest( + new String[] { + "X.java", + """ + public class X { + static /* @NonNull */ Object foo(/*@NonNull */ Object o) { + switch (o) { + case String s: + default: + System.out.println(); + } + return o; + } + } + class X2 { + static /* @NonNull */ Object foo(/*@NonNull */ Object o) { + switch (o) { + case String s: + default: + System.out.println(s); + } + return o; + } + } + class X3 { + static Object foo( Object o) { + switch (o) { + case String _ : + default : + System.out.println(); + } + return o; + } + } + class X4 { + static /* @NonNull */ Object foo(/*@NonNull */ Object o) { + switch (o) { + case String s: + System.out.println(); // first println + default: + System.out.println(); + } + return o; + } + } + """ + }, + "----------\n" + + "1. ERROR in X.java (at line 4)\n" + + " case String s:\n" + + " ^^^^^^^^^^^^^\n" + + "Illegal fall-through from a case label pattern\n" + + "----------\n" + + "2. ERROR in X.java (at line 16)\n" + + " System.out.println(s);\n" + + " ^\n" + + "s cannot be resolved to a variable\n" + + "----------\n"); + } + + // https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3320 + // [Enhanced Switch][Patterns] ECJ generated code hangs + public void testIssue3320() { + runConformTest( + new String[] { + "X.java", + """ + public class X { + static boolean guard(String prefix) { + System.out.println(prefix + " guard"); + return false; + } + public static void main(String [] args) { + Object o = new Object(); + switch (o) { + case String _, Object _ when guard("First") -> System.out.println("First case"); // first println + case String _, Object _ when guard("Second") -> System.out.println("Second case"); // first println + default -> System.out.println("Default"); + } + } + } + """ + }, + "First guard\nSecond guard\nDefault"); + } } diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java index 8ab6480e819..dadfb333677 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SwitchTest.java @@ -187,14 +187,9 @@ public void test007() { "}", }, "----------\n" + - "1. ERROR in p\\X.java (at line 5)\n" + - " case (int) (1.0 / 0.0) :\n" + - " ^^^^^^^^^^^^^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in p\\X.java (at line 7)\n" + + "1. ERROR in p\\X.java (at line 7)\n" + " case (int) (2.0 / 0.0) :\n" + - " ^^^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^^^^^^^^^^^^\n" + "Duplicate case\n" + "----------\n" ); @@ -1095,29 +1090,11 @@ public void testCaseTypeMismatch3() { public void testDuplicateCase() { String newMessage = "----------\n" + - "1. ERROR in X.java (at line 4)\n" + - " case \"123\": break;\n" + - " ^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 5)\n" + + "1. ERROR in X.java (at line 5)\n" + " case \"123\": break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "3. ERROR in X.java (at line 6)\n" + - " default: return args;\n" + - " ^^^^^^^^^^^^\n" + - "Void methods cannot return a value\n" + - "----------\n"; - - String oldMessage = - "----------\n" + - "1. ERROR in X.java (at line 3)\n" + - " switch(args[0]) {\n" + - " ^^^^^^^\n" + - "Cannot switch on a value of type String for source level below 1.7. Only convertible int values or enum variables are permitted\n" + - "----------\n" + "2. ERROR in X.java (at line 6)\n" + " default: return args;\n" + " ^^^^^^^^^^^^\n" + @@ -1136,72 +1113,54 @@ public void testDuplicateCase() { " }\n" + "}\n", }, - this.complianceLevel >= JDKLevelSupportingStringSwitch ? newMessage : oldMessage); + newMessage); } // JDK7: Strings in Switch. public void testDuplicateCase2() { String newMessage = "----------\n" + - "1. ERROR in X.java (at line 9)\n" + + "1. ERROR in X.java (at line 10)\n" + " case \"123\": break;\n" + - " ^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 10)\n" + - " case \"123\": break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "3. ERROR in X.java (at line 11)\n" + + "2. ERROR in X.java (at line 11)\n" + " case \"1\" + \"2\" + \"3\": break;\n" + - " ^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^^^^^^^^^^\n" + "Duplicate case\n" + "----------\n" + - "4. ERROR in X.java (at line 13)\n" + + "3. ERROR in X.java (at line 13)\n" + " case local: break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "5. ERROR in X.java (at line 14)\n" + + "4. ERROR in X.java (at line 14)\n" + " case field: break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "6. ERROR in X.java (at line 15)\n" + + "5. ERROR in X.java (at line 15)\n" + " case ifield: break;\n" + " ^^^^^^\n" + "Cannot make a static reference to the non-static field ifield\n" + "----------\n" + - "7. ERROR in X.java (at line 16)\n" + + "6. ERROR in X.java (at line 16)\n" + " case inffield: break;\n" + " ^^^^^^^^\n" + "Cannot make a static reference to the non-static field inffield\n" + "----------\n" + - "8. ERROR in X.java (at line 19)\n" + - " default: break;\n" + - " ^^^^^^^\n" + - "The default case is already defined\n" + - "----------\n"; - - String oldMessage = - "----------\n" + - "1. ERROR in X.java (at line 8)\n" + - " switch(args[0]) {\n" + - " ^^^^^^^\n" + - "Cannot switch on a value of type String for source level below 1.7. Only convertible int values or enum variables are permitted\n" + - "----------\n" + - "2. ERROR in X.java (at line 15)\n" + - " case ifield: break;\n" + - " ^^^^^^\n" + - "Cannot make a static reference to the non-static field ifield\n" + + "7. ERROR in X.java (at line 17)\n" + + " case nffield: break;\n" + + " ^^^^^^^\n" + + "case expressions must be constant expressions\n" + "----------\n" + - "3. ERROR in X.java (at line 16)\n" + - " case inffield: break;\n" + + "8. ERROR in X.java (at line 18)\n" + + " case argument: break;\n" + " ^^^^^^^^\n" + - "Cannot make a static reference to the non-static field inffield\n" + + "case expressions must be constant expressions\n" + "----------\n" + - "4. ERROR in X.java (at line 19)\n" + + "9. ERROR in X.java (at line 19)\n" + " default: break;\n" + " ^^^^^^^\n" + "The default case is already defined\n" + @@ -1232,7 +1191,7 @@ public void testDuplicateCase2() { " }\n" + "}\n" }, - this.complianceLevel >= JDKLevelSupportingStringSwitch ? newMessage : oldMessage); + newMessage); } // JDK7: Strings in Switch. public void testVariableCase() { @@ -1366,19 +1325,6 @@ public void testNullCase() { " case true ? (String) null : (String) null : break;\n" + " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + "case expressions must be constant expressions\n" + - "----------\n" + - "7. WARNING in X.java (at line 12)\n" + - " case true ? (String) null : (String) null : break;\n" + - " ^^^^^^^^^^^^^\n" + - "Dead code\n" + - "----------\n"; - - String oldMessage = - "----------\n" + - "1. ERROR in X.java (at line 6)\n" + - " switch(args[0]) {\n" + - " ^^^^^^^\n" + - "Cannot switch on a value of type String for source level below 1.7. Only convertible int values or enum variables are permitted\n" + "----------\n"; this.runNegativeTest(new String[] { @@ -1399,51 +1345,48 @@ public void testNullCase() { " }\n" + "}\n" }, - this.complianceLevel >= JDKLevelSupportingStringSwitch ? newMessage : oldMessage); + newMessage); } // JDK7: Strings in Switch. public void testDuplicateCase3() { String newMessage = "----------\n" + - "1. ERROR in X.java (at line 9)\n" + - " case \"123\": break;\n" + - " ^^^^^^^^^^\n" + - "Duplicate case\n" + - "----------\n" + - "2. ERROR in X.java (at line 10)\n" + + "1. ERROR in X.java (at line 10)\n" + " case \"1\" + \"2\" + \"3\": break;\n" + - " ^^^^^^^^^^^^^^^^^^^^\n" + + " ^^^^^^^^^^^^^^^\n" + "Duplicate case\n" + "----------\n" + - "3. ERROR in X.java (at line 12)\n" + + "2. ERROR in X.java (at line 12)\n" + " case local: break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "4. ERROR in X.java (at line 13)\n" + + "3. ERROR in X.java (at line 13)\n" + " case field: break;\n" + - " ^^^^^^^^^^\n" + + " ^^^^^\n" + "Duplicate case\n" + "----------\n" + - "5. ERROR in X.java (at line 14)\n" + + "4. ERROR in X.java (at line 14)\n" + " case ifield: break;\n" + - " ^^^^^^^^^^^\n" + + " ^^^^^^\n" + "Duplicate case\n" + "----------\n" + - "6. ERROR in X.java (at line 18)\n" + - " default: break;\n" + - " ^^^^^^^\n" + - "The default case is already defined\n" + - "----------\n"; - - String oldMessage = + "5. ERROR in X.java (at line 15)\n" + + " case inffield: break;\n" + + " ^^^^^^^^\n" + + "case expressions must be constant expressions\n" + "----------\n" + - "1. ERROR in X.java (at line 8)\n" + - " switch(args[0]) {\n" + - " ^^^^^^^\n" + - "Cannot switch on a value of type String for source level below 1.7. Only convertible int values or enum variables are permitted\n" + + "6. ERROR in X.java (at line 16)\n" + + " case nffield: break;\n" + + " ^^^^^^^\n" + + "case expressions must be constant expressions\n" + "----------\n" + - "2. ERROR in X.java (at line 18)\n" + + "7. ERROR in X.java (at line 17)\n" + + " case argument: break;\n" + + " ^^^^^^^^\n" + + "case expressions must be constant expressions\n" + + "----------\n" + + "8. ERROR in X.java (at line 18)\n" + " default: break;\n" + " ^^^^^^^\n" + "The default case is already defined\n" + @@ -1473,7 +1416,7 @@ public void testDuplicateCase3() { " }\n" + "}\n" }, - this.complianceLevel >= JDKLevelSupportingStringSwitch ? newMessage : oldMessage); + newMessage); } public void testDuplicateHashCode() { @@ -3569,6 +3512,33 @@ static public void main (String[] args) { "42"); } +public void testNonConstantCase() throws Exception { + if (this.complianceLevel < ClassFileConstants.JDK14) + return; + this.runNegativeTest(new String[] { + "X.java", + """ + public class X { + public static void main(String[] args) { + Integer i = Integer.valueOf(-1); + int b = 10; + switch (i) { + case b -> System.out.println("Null"); + case -1 -> System.out.println(-1); + default -> System.out.println("default"); + } + } + } + """, + }, + "----------\n" + + "1. ERROR in X.java (at line 6)\n" + + " case b -> System.out.println(\"Null\");\n" + + " ^\n" + + "case expressions must be constant expressions\n" + + "----------\n"); +} + public static Class testClass() { return SwitchTest.class; } diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java index 83e6a021b89..f51e9b1345f 100644 --- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java +++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/RunVariousSwitchTests.java @@ -47,6 +47,9 @@ public static Class[] getAllTestClasses() { UnnamedPatternsAndVariablesTest.class, JEP441SnippetsTest.class, FlowAnalysisTest.class, + EnumTest.class, + LocalEnumTest.class, + ConstantTest.class, JavaSearchBugs14SwitchExpressionTests.class, ASTRewritingSwitchExpressionsTest.class,