From 2bc7b0e311a1a2c605a76c4f0cb594bd0372ad23 Mon Sep 17 00:00:00 2001 From: Stephan Herrmann Date: Thu, 29 Aug 2024 15:03:52 +0200 Subject: [PATCH] [23] JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) + systematically test switch with numerical narrowing + switch on long,float,double,boolean (+boxed) + check constant type (must be same or unboxed of) + implement code gen (must use indy #typeSwitch) - except Boolean: here we optimize towards unboxing + support more signatures for indy typeSwitch + fix duplicate detection for boolean constants - while observing that null and -1 share the same IntConstant(-1)! --- .../eclipse/jdt/core/compiler/IProblem.java | 7 + .../jdt/internal/compiler/ClassFile.java | 4 +- .../internal/compiler/ast/CaseStatement.java | 19 ++ .../compiler/ast/SwitchStatement.java | 34 ++- .../compiler/problem/ProblemReporter.java | 8 + .../compiler/problem/messages.properties | 3 + .../regression/CompilerInvocationTests.java | 2 + .../regression/PrimitiveInPatternsTestSH.java | 271 +++++++++++++++++- 8 files changed, 339 insertions(+), 9 deletions(-) diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java index 4e6a6ca33b1..9b73b9d0472 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/core/compiler/IProblem.java @@ -2683,4 +2683,11 @@ public interface IProblem { * @noreference preview feature */ int ConstructorCallNotAllowedHere = PreviewRelated + 2031; + + /** + * @since 3.39 + * @noreference preview feature + */ + int WrongCaseType = PreviewRelated + 2100; + } 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 5e05aacb48a..234007215ea 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 @@ -4048,8 +4048,10 @@ private int addBootStrapTypeSwitchEntry(int localContentsOffset, SwitchStatement this.constantPool.literalIndexForDynamic(c.primitivesBootstrapIdx, c.c.booleanValue() ? BooleanConstant.TRUE_STRING : BooleanConstant.FALSE_STRING, ConstantPool.JavaLangBooleanSignature); - case TypeIds.T_byte, TypeIds.T_char, TypeIds.T_short, TypeIds.T_int, TypeIds.T_long -> + 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()); case TypeIds.T_float -> this.constantPool.literalIndex(c.c.floatValue()); case TypeIds.T_double -> 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 5915c80081d..7b33fd14412 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 @@ -235,6 +235,25 @@ public ResolvedCase[] resolveCase(BlockScope scope, TypeBinding switchExpression } } } else { + // check from ยง14.11.1 (JEP 455): + // For each case constant associated with the switch block that is a constant expression, one of the following is true: + // - [...] + // - 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; + } + switchExpressionType = expectedCaseType; + } + } + // Constant con = resolveConstantExpression(scope, caseType, switchExpressionType, switchStatement, e, cases); if (con != Constant.NotAConstant) { int index = this == switchStatement.nullCase && e instanceof NullLiteral ? 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 880aa932d0b..570cdcbfc50 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 @@ -33,6 +33,7 @@ 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.classfmt.ClassFileConstants; @@ -808,6 +809,9 @@ public void generateCode(BlockScope currentScope, CodeStream codeStream) { 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 if (hasCases) { @@ -1025,9 +1029,7 @@ private void generateCodeSwitchPatternPrologue(BlockScope currentScope, CodeStre } private void generateTypeSwitchPatternPrologue(CodeStream codeStream, int invokeDynamicNumber) { TypeBinding exprType = this.expression.resolvedType; - char[] signature = (this.switchBits & Primitive) != 0 - ? "(XI)I".replace("X", String.valueOf(exprType.signature())).toCharArray() //$NON-NLS-1$ //$NON-NLS-2$ - : "(Ljava/lang/Object;I)I".toCharArray(); //$NON-NLS-1$ + char[] signature =typeSwitchSignature(exprType); int argsSize = TypeIds.getCategory(exprType.id) + 1; // Object | PRIM, restartIndex (PRIM = Z|S|I..) codeStream.invokeDynamic(invokeDynamicNumber, argsSize, @@ -1036,6 +1038,17 @@ private void generateTypeSwitchPatternPrologue(CodeStream codeStream, int invoke signature, TypeBinding.INT); } + char[] typeSwitchSignature(TypeBinding exprType) { + char[] arg1 = switch (exprType.id) { + case TypeIds.T_JavaLangLong, TypeIds.T_JavaLangFloat, TypeIds.T_JavaLangDouble, TypeIds.T_JavaLangBoolean -> + exprType.signature(); + default -> + (this.switchBits & Primitive) != 0 + ? exprType.signature() + : "Ljava/lang/Object;".toCharArray(); //$NON-NLS-1$ + }; + return CharOperation.concat("(".toCharArray(), arg1, "I)I".toCharArray()); //$NON-NLS-1$ //$NON-NLS-2$ + } private void generateEnumSwitchPatternPrologue(CodeStream codeStream, int invokeDynamicNumber) { String genericTypeSignature = new String(this.expression.resolvedType.genericTypeSignature()); String callingParams = "(" + genericTypeSignature + "I)I"; //$NON-NLS-1$ //$NON-NLS-2$ @@ -1207,12 +1220,16 @@ public void resolve(BlockScope upperScope) { } for (int j = 0; j < counter; j++) { IntPredicate check = idx -> { - Constant c2 = this.otherConstants[idx].c; + 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; return this.constants[idx] == c1; @@ -1243,9 +1260,7 @@ public void resolve(BlockScope upperScope) { } else { if (!c.isPattern() && check.test(j)) { if (this.isNonTraditional) { - if (c.e instanceof NullLiteral && this.otherConstants[j].e instanceof NullLiteral) { - reportDuplicateCase(c.e, this.otherConstants[j].e, length); - } + reportDuplicateCase(c.e, this.otherConstants[j].e, length); } else { reportDuplicateCase(caseStmt, this.cases[caseIndex[j]], length); } @@ -1475,6 +1490,11 @@ private boolean needPatternDispatchCopy() { TypeBinding eType = this.expression != null ? this.expression.resolvedType : null; if (eType == null) return false; + switch (eType.id) { + case TypeIds.T_JavaLangLong, TypeIds.T_JavaLangFloat, TypeIds.T_JavaLangDouble: + return true; + // note: if no patterns are present we optimize Boolean to use unboxing rather than indy typeSwitch + } return !(eType.isPrimitiveOrBoxedPrimitiveType() || eType.isEnum() || eType.id == TypeIds.T_JavaLangString); // classic selectors } private void addSecretPatternSwitchVariables(BlockScope upperScope) { diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java index eedf2bf2c4c..9763d988761 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/ProblemReporter.java @@ -1660,6 +1660,14 @@ public void caseExpressionMustBeConstant(Expression expression) { expression.sourceStart, expression.sourceEnd); } +public void caseExpressionWrongType(Expression expression, TypeBinding switchBinding, TypeBinding selectorBinding) { + this.handle( + IProblem.WrongCaseType, + new String[] {String.valueOf(switchBinding.readableName()), String.valueOf(selectorBinding.readableName())}, + new String[] {String.valueOf(switchBinding.shortReadableName()), String.valueOf(selectorBinding.shortReadableName())}, + expression.sourceStart, + expression.sourceEnd); +} public void classExtendFinalClass(SourceTypeBinding type, TypeReference superclass, TypeBinding superTypeBinding) { String name = new String(type.sourceName()); String superTypeFullName = new String(superTypeBinding.readableName()); diff --git a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties index 90bc9415780..2326802a998 100644 --- a/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties +++ b/org.eclipse.jdt.core.compiler.batch/src/org/eclipse/jdt/internal/compiler/problem/messages.properties @@ -1193,6 +1193,9 @@ 2030 = Cannot assign field ''{0}'' in an early construction context, because it has an initializer 2031 = Constructor call is not allowed here +# JEP 455 Primitive Types in Patterns, instanceof, and switch (Preview) +2100 = Case constants in a switch on ''{0}'' must have type ''{1}'' + ### ELABORATIONS ## Access restrictions 78592 = The type ''{1}'' is not API (restriction on classpath entry ''{0}'') diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java index d3b1253de05..9882952c33b 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/CompilerInvocationTests.java @@ -1218,6 +1218,7 @@ class ProblemAttributes { expectedProblemAttributes.put("WildcardConstructorInvocation", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("WildcardFieldAssignment", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("WildcardMethodInvocation", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); + expectedProblemAttributes.put("WrongCaseType", new ProblemAttributes(CategorizedProblem.CAT_PREVIEW_RELATED)); expectedProblemAttributes.put("illFormedParameterizationOfFunctionalInterface", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("lambdaParameterTypeMismatched", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); expectedProblemAttributes.put("lambdaSignatureMismatched", new ProblemAttributes(CategorizedProblem.CAT_TYPE)); @@ -2333,6 +2334,7 @@ class ProblemAttributes { expectedProblemAttributes.put("WildcardConstructorInvocation", SKIP); expectedProblemAttributes.put("WildcardFieldAssignment", SKIP); expectedProblemAttributes.put("WildcardMethodInvocation", SKIP); + expectedProblemAttributes.put("WrongCaseType", SKIP); expectedProblemAttributes.put("illFormedParameterizationOfFunctionalInterface", SKIP); expectedProblemAttributes.put("lambdaParameterTypeMismatched", SKIP); expectedProblemAttributes.put("lambdaSignatureMismatched", SKIP); 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 c73104038a3..7fc14e9801f 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 @@ -35,6 +35,9 @@ public class PrimitiveInPatternsTestSH extends AbstractRegressionTest9 { private static final String[] MAXVALUES = { "true", "Byte.MAX_VALUE", "'z'", "Short.MAX_VALUE", "Integer.MAX_VALUE", "Long.MAX_VALUE", "Float.MAX_VALUE", "Double.MAX_VALUE" }; private static final String[] GOODVALUES = { "true", "49", "'1'", "49", "49", "49L", "49.0f", "49.0d" }; // 49 ~ '1' private static final String[] NEGVALUES = { "false", "-1", "'-'", "-1", "-1", "-1L", "-1.0f", "-1.0d" }; + + // larger then MAX of previous type, still needs suffix added via toConstantOfType + private static final String[] CONSTANTS = { "true", "1", "'1'", "300", "40000", "5000000000", "6.0E20", "7.0E40" }; private static final boolean[] IS_NUMERICAL = { false, true, false, true, true, true, true, true }; private static String MAX_VALUES_STRING = "true|127|z|32767|2147483647|9223372036854775807|3.4028235E38|1.7976931348623157E308|"; /** @@ -59,6 +62,16 @@ private static String fillInMax(String template, int idx, boolean useMax) { .replace("NEGVAL", NEGVALUES[idx]).replace("VAL", useMax ? MAXVALUES[idx] : GOODVALUES[idx]); } + static String toConstantOfType(String constVal, String ptype) { + return switch (ptype) { + case "long" -> constVal+"L"; + case "float" -> constVal+"f"; + case "double" -> constVal+"d"; + default -> constVal; + }; + } + + static { // TESTS_NUMBERS = new int [] { 1 }; // TESTS_RANGE = new int[] { 1, -1 }; @@ -453,7 +466,6 @@ public void testWideningFloat() { testWideningFrom_both("float", 6, true, String.valueOf((double) Float.MAX_VALUE)+"|"); } - // Narrowing Primitive Double private void testNarrowingFrom(String from, int idx, boolean useMax, String expectedOut) { assert from.equals(PRIMITIVES[idx]) : "mismatch between from and idx"; // example (from="short", idx=3, useMax=false): @@ -1154,6 +1166,263 @@ public static void main(String... args) { "true|v=false|1.0|1.5|v=1.6|"); } + private void testNarrowingInSwitchFrom(String from, int idx, String expectedOut) { + // case statements (constant & type pattern) apply narrowing to each smaller numerical type + + assert from.equals(PRIMITIVES[idx]) : "mismatch between from and idx"; + // example (from="short", idx=3): + // public class X { + // public static int doswitch(short v) { // return type: at least 'int' or + // return switch(v) { + // case 1 -> 10; + // case byte vv -> 10+vv; + // case 300 -> 30; + // case short vv -> 30+vv; + // } + // } + // static void print(Object o) { + // System.out.print(o); + // System.out.print('|'); + // } + // public static void main(String[] args) { + // print(X.doswitch((short)1); + // print(X.doswitch((short)(1+1)); + // print(X.doswitch((short)300); + // print(X.doswitch((short)(300+300)); + // } + // } + + String classTmpl = + """ + public class X { + public static RET doswitch(FROM v) { + return switch(v) { + BODY + }; + } + static void print(Object o) { + System.out.print(o); + System.out.print('|'); + } + public static void main(String[] args) { + CALLS + } + } + """; + String casesTmpl = + """ + case CONST -> VAL; + case PRIM vv -> VAL+vv; + """; + String callsTmpl = + """ + print(X.doswitch((FROM)CONST)); + print(X.doswitch((FROM)(CONST+CONST))); + """; + // for all numerical primitive types up-to 'from': + StringBuilder cases = new StringBuilder(); + StringBuilder calls = new StringBuilder(); + for (int i = 0; i <= idx; i++) { + if (!IS_NUMERICAL[i]) continue; + String constVal = toConstantOfType(CONSTANTS[i], from); + String val10 = String.valueOf(i*10); + cases.append(casesTmpl.replaceAll("PRIM", PRIMITIVES[i]).replace("CONST", constVal).replace("VAL", val10)); + calls.append(callsTmpl.replaceAll("FROM", from).replaceAll("CONST", constVal)); + } + String retType = idx <= 4 /*int*/ ? "int" : from; // no syntax exists for constants below int + String classX = classTmpl.replace("FROM", from).replace("RET", retType) + .replace("BODY", cases.toString()) + .replace("CALLS", calls.toString()); + runConformTest(new String[] { "X.java", classX }, expectedOut); + } + public void testNarrowingInSwitchFromShort() { + testNarrowingInSwitchFrom("short", 3, "10|12|30|630|"); + } + public void testNarrowingInSwitchFromInt() { + testNarrowingInSwitchFrom("int", 4, "10|12|30|630|40|80040|"); + } + public void testNarrowingInSwitchFromLong() { + testNarrowingInSwitchFrom("long", 5, "10|12|30|630|40|80040|50|10000000050|"); + } + public void testNarrowingInSwitchFromFloat() { + testNarrowingInSwitchFrom("float", 6, "10.0|12.0|30.0|630.0|40.0|80040.0|50.0|1.0E10|60.0|1.2E21|"); + } + public void testNarrowingInSwitchFromDouble() { + testNarrowingInSwitchFrom("double", 7, "10.0|12.0|30.0|630.0|40.0|80040.0|50.0|1.000000005E10|60.0|1.2E21|70.0|1.4E41|"); + } + + public void testSwitchOn_long_wrongSelector() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + int m1(long in) { + return switch(in) { + case 1 -> 1; + case 'a' -> 2; + case 3L -> 3; + case 4.0f -> 4; + case 5.0d -> 5; + default -> -1; + }; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 4) + case 1 -> 1; + ^ + Case constants in a switch on 'long' must have type 'long' + ---------- + 2. ERROR in X.java (at line 5) + case 'a' -> 2; + ^^^ + Case constants in a switch on 'long' must have type 'long' + ---------- + 3. ERROR in X.java (at line 7) + case 4.0f -> 4; + ^^^^ + Case constants in a switch on 'long' must have type 'long' + ---------- + 4. ERROR in X.java (at line 8) + case 5.0d -> 5; + ^^^^ + Case constants in a switch on 'long' must have type 'long' + ---------- + """); + } + public void testSwitchOn_Float_wrongSelector() { + runNegativeTest(new String[] { + "X.java", + """ + public class X { + int m1(Float in) { + return switch(in) { + case 1 -> 1; + case 'a' -> 2; + case 3L -> 3; + case 4.0f -> 4; + case 5.0d -> 5; + default -> -1; + }; + } + } + """ + }, + """ + ---------- + 1. ERROR in X.java (at line 4) + case 1 -> 1; + ^ + Case constants in a switch on 'Float' must have type 'float' + ---------- + 2. ERROR in X.java (at line 5) + case 'a' -> 2; + ^^^ + Case constants in a switch on 'Float' must have type 'float' + ---------- + 3. ERROR in X.java (at line 6) + case 3L -> 3; + ^^ + Case constants in a switch on 'Float' must have type 'float' + ---------- + 4. ERROR in X.java (at line 8) + case 5.0d -> 5; + ^^^^ + Case constants in a switch on 'Float' must have type 'float' + ---------- + """); + } + public void testSwitchOnBoxed_OK() { + // constant cases for all boxed primitive types except Boolean + // run as separate tests. + String classTmpl = """ + public class XBOX { + static int m1(BOX in) { + return switch(in) { + case VAL -> 1; + case MAX -> 2; + default -> -2; + }; + } + public static void main(String... args) { + CALLS + } + } + """; + String callsTmpl = + """ + System.out.print(m1((PRIM)VAL)); + System.out.print(m1((PRIM)MAX)); + System.out.print(m1((PRIM)NEGVAL)); + """; + // for all primitive types other than boolean (boolean would have duplicate cases): + for (int i = 1; i < PRIMITIVES.length; i++) { // 1 + String calls = fillIn(callsTmpl, i); + String classX = fillIn(classTmpl, i) + .replace("CALLS", calls); + runConformTest(new String[] { "XBOX.java".replace("BOX", BOXES[i]), classX }, "12-2"); + } + } + public void testSwitchOn_Boolean_OK() { + runConformTest(new String[] { + "X.java", + """ + public class X { + static int m1(Boolean in) { + return switch(in) { + case true-> 1; + default -> -1; + }; + } + public static void main(String... args) { + System.out.print(m1(true)); + System.out.print(m1(false)); + } + } + """ + }, + "1-1"); + } + + public void testDuplicateBoolCase() { + // saw SOE when executing bogus byte code: + runNegativeTest(new String[] { + "XBoolean.java", + """ + public class XBoolean { + static int m1(Boolean in) { + return switch(in) { + case true -> 1; + case true -> 2; + default -> -1; + }; + } + public static void main(String... args) { + System.out.print(m1(true)); + System.out.print(m1(true)); + System.out.print(m1(false)); + + } + } + """ + }, + """ + ---------- + 1. ERROR in XBoolean.java (at line 4) + case true -> 1; + ^^^^ + Duplicate case + ---------- + 2. ERROR in XBoolean.java (at line 5) + case true -> 2; + ^^^^ + Duplicate case + ---------- + """); + } // test from spec public void _testSpec001() { runConformTest(new String[] {