1 import jdk.incubator.code.CodeReflection;
2 import jdk.incubator.code.OpTransformer;
3 import jdk.incubator.code.dialect.core.CoreOp;
4 import jdk.incubator.code.extern.OpWriter;
5 import jdk.incubator.code.interpreter.Interpreter;
6 import org.junit.jupiter.api.Assertions;
7 import org.junit.jupiter.api.Test;
8
9 import java.io.IOException;
10 import java.io.OutputStream;
11 import java.io.StringWriter;
12 import java.lang.invoke.MethodHandles;
13 import java.lang.reflect.Method;
14 import java.util.*;
15 import java.util.stream.Stream;
16
17 /*
18 * @test
19 * @modules jdk.incubator.code
20 * @run junit TestSwitchExpressionOp
21 */
22 public class TestSwitchExpressionOp {
23
24 @Test
25 void testCasePatternGuard() {
26 CoreOp.FuncOp lmodel = lower("casePatternGuard");
27 Object[] args = {"c++", "java", new R(8), new R(2L), new R(3f), new R(4.0)};
28 for (Object arg : args) {
29 Assertions.assertEquals(casePatternGuard(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
30 }
31 }
32 @CodeReflection
33 static String casePatternGuard(Object obj) {
34 return switch (obj) {
35 case String s when s.length() > 3 -> "str with length > %d".formatted(s.length());
36 case R(Number n) when n.getClass().equals(Double.class) -> "R(Double)";
37 default -> "else";
38 };
39 }
40
41 @Test
42 void testCaseRecordPattern() {
43 // @@@ new R(null) must match the pattern R(Number c), but it doesn't
44 // @@@ test with generic record
45 CoreOp.FuncOp lmodel = lower("caseRecordPattern");
46 Object[] args = {new R(8), new R(1.0), new R(2L), "abc"};
47 for (Object arg : args) {
48 Assertions.assertEquals(caseRecordPattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
49 }
50 }
51 record R(Number n) {}
52 @CodeReflection
53 static String caseRecordPattern(Object o) {
54 return switch (o) {
55 case R(Number _) -> "R(_)";
56 default -> "else";
57 };
58 }
59 @Test
60 void testCaseTypePattern() {
61 CoreOp.FuncOp lmodel = lower("caseTypePattern");
62 Object[] args = {"str", new ArrayList<>(), new int[]{}, new Stack[][]{}, new Collection[][][]{}, 8, 'x'};
63 for (Object arg : args) {
64 Assertions.assertEquals(caseTypePattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
65 }
66 }
67 @CodeReflection
68 static String caseTypePattern(Object o) {
69 return switch (o) {
70 case String _ -> "String"; // class
71 case RandomAccess _ -> "RandomAccess"; // interface
72 case int[] _ -> "int[]"; // array primitive
73 case Stack[][] _ -> "Stack[][]"; // array class
74 case Collection[][][] _ -> "Collection[][][]"; // array interface
75 case final Number n -> "Number"; // final modifier
76 default -> "something else";
77 };
78 }
79
80 @Test
81 void testCasePatternWithCaseConstant() {
82 CoreOp.FuncOp lmodel = lower("casePatternWithCaseConstant");
83 int[] args = {42, 43, -44, 0};
84 for (int arg : args) {
85 Assertions.assertEquals(casePatternWithCaseConstant(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
86 }
87 }
88
89 @CodeReflection
90 static String casePatternWithCaseConstant(Integer a) {
91 return switch (a) {
92 case 42 -> "forty two";
93 // @@@ case int will not match, because of the way InstanceOfOp is interpreted
94 case Integer i when i > 0 -> "positive int";
95 case Integer i when i < 0 -> "negative int";
96 default -> "zero";
97 };
98 }
99
100 // @Test
101 void testCasePatternMultiLabel() {
102 CoreOp.FuncOp lmodel = lower("casePatternMultiLabel");
103 Object[] args = {(byte) 1, (short) 2, 'A', 3, 4L, 5f, 6d, true, "str"};
104 for (Object arg : args) {
105 Assertions.assertEquals(casePatternMultiLabel(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
106 }
107 }
108 // @CodeReflection
109 // code model for such as code is not supported
110 // @@@ support this case and uncomment its test
111 private static String casePatternMultiLabel(Object o) {
112 return switch (o) {
113 case Integer _, Long _, Character _, Byte _, Short _-> "integral type";
114 default -> "non integral type";
115 };
116 }
117
118 @Test
119 void testCasePatternThrow() {
120 CoreOp.FuncOp lmodel = lower("casePatternThrow");
121
122 Object[] args = {Byte.MAX_VALUE, Short.MIN_VALUE, 0, 1L, 11f, 22d};
123 for (Object arg : args) {
124 Assertions.assertThrows(IllegalArgumentException.class, () -> Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
125 }
126
127 Object[] args2 = {"abc", List.of()};
128 for (Object arg : args2) {
129 Assertions.assertEquals(casePatternThrow(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
130 }
131 }
132
133 @CodeReflection
134 private static String casePatternThrow(Object o) {
135 return switch (o) {
136 case Number n -> throw new IllegalArgumentException();
137 case String s -> "a string";
138 default -> o.getClass().getName();
139 };
140 }
141
142 @Test
143 void testCasePatternBehaviorIsSyntaxIndependent() {
144 CoreOp.FuncOp ruleExpression = lower("casePatternRuleExpression");
145 CoreOp.FuncOp ruleBlock = lower("casePatternRuleBlock");
146 CoreOp.FuncOp statement = lower("casePatternStatement");
147
148 Object[] args = {1, "2", 3L};
149
150 for (Object arg : args) {
151 Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), ruleBlock, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
152 Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), statement, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
153 }
154 }
155
156 @CodeReflection
157 private static String casePatternRuleExpression(Object o) {
158 return switch (o) {
159 case Integer i -> "integer";
160 case String s -> "string";
161 default -> "not integer nor string";
162 };
163 }
164
165 @CodeReflection
166 private static String casePatternRuleBlock(Object o) {
167 return switch (o) {
168 case Integer i -> {
169 yield "integer";
170 }
171 case String s -> {
172 yield "string";
173 }
174 default -> {
175 yield "not integer nor string";
176 }
177 };
178 }
179
180 @CodeReflection
181 private static String casePatternStatement(Object o) {
182 return switch (o) {
183 case Integer i: yield "integer";
184 case String s: yield "string";
185 default: yield "not integer nor string";
186 };
187 }
188
189 @Test
190 void testCaseConstantOtherKindsOfExpr() {
191 CoreOp.FuncOp lmodel = lower("caseConstantOtherKindsOfExpr");
192 for (int i = 0; i < 14; i++) {
193 Assertions.assertEquals(caseConstantOtherKindsOfExpr(i), Interpreter.invoke(MethodHandles.lookup(), lmodel, i));
194 }
195 }
196
197 static class Constants {
198 static final int c1 = 12;
199 }
200
201 @CodeReflection
202 private static String caseConstantOtherKindsOfExpr(int i) {
203 final int eleven = 11;
204 return switch (i) {
205 case 1 & 0xF -> "1";
206 case 4>>1 -> "2";
207 case (int) 3L -> "3";
208 case 2<<1 -> "4";
209 case 10 / 2 -> "5";
210 case 12 - 6 -> "6";
211 case 3 + 4 -> "7";
212 case 2 * 2 * 2 -> "8";
213 case 8 | 1 -> "9";
214 case (10) -> "10";
215 case eleven -> "11";
216 case Constants.c1 -> String.valueOf(Constants.c1);
217 case 1 > 0 ? 13 : 133 -> "13";
218 default -> "an int";
219 };
220 }
221
222 @Test
223 void testCaseConstantEnum() {
224 CoreOp.FuncOp lmodel = lower("caseConstantEnum");
225 for (Day day : Day.values()) {
226 Assertions.assertEquals(caseConstantEnum(day), Interpreter.invoke(MethodHandles.lookup(), lmodel, day));
227 }
228 }
229
230 enum Day {
231 MON, TUE, WED, THU, FRI, SAT, SUN
232 }
233
234 @CodeReflection
235 private static int caseConstantEnum(Day d) {
236 return switch (d) {
237 case MON, FRI, SUN -> 6;
238 case TUE -> 7;
239 case THU, SAT -> 8;
240 case WED -> 9;
241 };
242 }
243
244 @Test
245 void testCaseConstantFallThrough() {
246 CoreOp.FuncOp lmodel = lower("caseConstantFallThrough");
247 char[] args = {'A', 'B', 'C'};
248 for (char arg : args) {
249 Assertions.assertEquals(caseConstantFallThrough(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
250 }
251 }
252
253 @CodeReflection
254 private static String caseConstantFallThrough(char c) {
255 return switch (c) {
256 case 'A':
257 case 'B':
258 yield "A or B";
259 default:
260 yield "Neither A nor B";
261 };
262 }
263
264 // @CodeReflection
265 // compiler code doesn't support case null, default
266 // @@@ support such as case and test the switch expression lowering for this case
267 private static String caseConstantNullAndDefault(String s) {
268 return switch (s) {
269 case "abc" -> "alphabet";
270 case null, default -> "null or default";
271 };
272 }
273
274 @Test
275 void testCaseConstantNullLabel() {
276 CoreOp.FuncOp lmodel = lower("caseConstantNullLabel");
277 String[] args = {null, "non null"};
278 for (String arg : args) {
279 Assertions.assertEquals(caseConstantNullLabel(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
280 }
281 }
282
283 @CodeReflection
284 private static String caseConstantNullLabel(String s) {
285 return switch (s) {
286 case null -> "null";
287 default -> "non null";
288 };
289 }
290
291 @Test
292 void testCaseConstantThrow() {
293 CoreOp.FuncOp lmodel = lower("caseConstantThrow");
294 Assertions.assertThrows(IllegalArgumentException.class, () -> Interpreter.invoke(MethodHandles.lookup(), lmodel, 8));
295 int[] args = {9, 10};
296 for (int arg : args) {
297 Assertions.assertEquals(caseConstantThrow(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
298 }
299 }
300
301 @CodeReflection
302 private static String caseConstantThrow(Integer i) {
303 return switch (i) {
304 case 8 -> throw new IllegalArgumentException();
305 case 9 -> "NINE";
306 default -> "An integer";
307 };
308 }
309
310 @Test
311 void testCaseConstantMultiLabels() {
312 CoreOp.FuncOp lmodel = lower("caseConstantMultiLabels");
313 char[] args = {'a', 'e', 'i', 'o', 'u', 'j', 'p', 'g'};
314 for (char arg : args) {
315 Assertions.assertEquals(caseConstantMultiLabels(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
316 }
317 }
318
319 @CodeReflection
320 private static String caseConstantMultiLabels(char c) {
321 return switch (Character.toLowerCase(c)) {
322 case 'a', 'e', 'i', 'o', 'u': yield "vowel";
323 default: yield "consonant";
324 };
325 }
326
327 @Test
328 void testCaseConstantBehaviorIsSyntaxIndependent() {
329 CoreOp.FuncOp ruleExpression = lower("caseConstantRuleExpression");
330 CoreOp.FuncOp ruleBlock = lower("caseConstantRuleBlock");
331 CoreOp.FuncOp statement = lower("caseConstantStatement");
332
333 String[] args = {"FOO", "BAR", "BAZ", "OTHER"};
334
335 for (String arg : args) {
336 Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), ruleBlock, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
337 Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), statement, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
338 }
339 }
340
341 @CodeReflection
342 public static String caseConstantRuleExpression(String r) {
343 return switch (r) {
344 case "FOO" -> "BAR";
345 case "BAR" -> "BAZ";
346 case "BAZ" -> "FOO";
347 default -> "";
348 };
349 }
350
351 @CodeReflection
352 public static String caseConstantRuleBlock(String r) {
353 return switch (r) {
354 case "FOO" -> {
355 yield "BAR";
356 }
357 case "BAR" -> {
358 yield "BAZ";
359 }
360 case "BAZ" -> {
361 yield "FOO";
362 }
363 default -> {
364 yield "";
365 }
366 };
367 }
368
369 @CodeReflection
370 private static String caseConstantStatement(String s) {
371 return switch (s) {
372 case "FOO": yield "BAR";
373 case "BAR": yield "BAZ";
374 case "BAZ": yield "FOO";
375 default: yield "";
376 };
377 }
378
379 @Test
380 void testCaseConstantConv() {
381 CoreOp.FuncOp lmodel = lower("caseConstantConv");
382 short[] args = {1, 2, 3, 4};
383 for (short arg : args) {
384 Assertions.assertEquals(caseConstantConv(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
385 }
386 }
387
388 @CodeReflection
389 static String caseConstantConv(short a) {
390 final short s = 1;
391 final byte b = 2;
392 return switch (a) {
393 case s -> "one"; // identity
394 case b -> "three"; // widening primitive conversion
395 case 3 -> "two"; // narrowing primitive conversion
396 default -> "default";
397 };
398 }
399
400 @Test
401 void testCaseConstantConv2() {
402 CoreOp.FuncOp lmodel = lower("caseConstantConv2");
403 Byte[] args = {1, 2, 3};
404 for (Byte arg : args) {
405 Assertions.assertEquals(caseConstantConv2(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
406 }
407 }
408
409 @CodeReflection
410 static String caseConstantConv2(Byte a) {
411 final byte b = 2;
412 return switch (a) {
413 case 1 -> "one"; // narrowing primitive conversion followed by a boxing conversion
414 case b -> "two"; // boxing
415 default -> "default";
416 };
417 }
418
419 @Test
420 void testUnconditionalPattern() {
421 CoreOp.FuncOp lmodel = lower("unconditionalPattern");
422 String[] args = {"A", "X"};
423 for (String arg : args) {
424 Assertions.assertEquals(unconditionalPattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
425 }
426 }
427
428 @CodeReflection
429 static String unconditionalPattern(String s) {
430 return switch (s) {
431 case "A" -> "A";
432 case Object o -> "default";
433 };
434 }
435
436
437 @Test
438 void testDefaultCaseNotTheLast() {
439 CoreOp.FuncOp lmodel = lower("defaultCaseNotTheLast");
440 String[] args = {"something", "M", "A"};
441 for (String arg : args) {
442 Assertions.assertEquals(defaultCaseNotTheLast(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
443 }
444 }
445
446 @CodeReflection
447 static String defaultCaseNotTheLast(String s) {
448 return switch (s) {
449 default -> "else";
450 case "M" -> "Mow";
451 case "A" -> "Aow";
452 };
453 }
454
455 // we are not testing switch expr that has no default,
456 // because to test for MatchException we need to set up separate compilation
457 // in compiler tests we are checking that the code model contains a default case that throws MatchException
458 // that should be enough
459
460 private static CoreOp.FuncOp lower(String methodName) {
461 return lower(getCodeModel(methodName));
462 }
463
464 private static CoreOp.FuncOp lower(CoreOp.FuncOp f) {
465 writeModel(f, System.out, OpWriter.LocationOption.DROP_LOCATION);
466
467 CoreOp.FuncOp lf = f.transform(OpTransformer.LOWERING_TRANSFORMER);
468 writeModel(lf, System.out, OpWriter.LocationOption.DROP_LOCATION);
469
470 return lf;
471 }
472
473 private static void writeModel(CoreOp.FuncOp f, OutputStream os, OpWriter.Option... options) {
474 StringWriter sw = new StringWriter();
475 new OpWriter(sw, options).writeOp(f);
476 try {
477 os.write(sw.toString().getBytes());
478 } catch (IOException e) {
479 throw new RuntimeException(e);
480 }
481 }
482
483 private static CoreOp.FuncOp getCodeModel(String methodName) {
484 Optional<Method> om = Stream.of(TestSwitchExpressionOp.class.getDeclaredMethods())
485 .filter(m -> m.getName().equals(methodName))
486 .findFirst();
487
488 return CoreOp.ofMethod(om.get()).get();
489 }
490 }