1 /*
  2  * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 import jdk.incubator.code.CodeTransformer;
 25 import jdk.incubator.code.Op;
 26 import jdk.incubator.code.Reflect;
 27 import jdk.incubator.code.dialect.core.CoreOp;
 28 import jdk.incubator.code.extern.OpWriter;
 29 import org.junit.jupiter.api.Assertions;
 30 import org.junit.jupiter.api.Test;
 31 
 32 import java.io.IOException;
 33 import java.io.OutputStream;
 34 import java.io.StringWriter;
 35 import java.lang.invoke.MethodHandles;
 36 import java.lang.reflect.Method;
 37 import java.util.*;
 38 import java.util.stream.Stream;
 39 
 40 /*
 41  * @test
 42  * @modules jdk.incubator.code
 43  * @library lib
 44  * @run junit TestSwitchStatementOp
 45  * @run main Unreflect TestSwitchStatementOp
 46  * @run junit TestSwitchStatementOp
 47  *
 48  */
 49 public class TestSwitchStatementOp {
 50 
 51     @Test
 52     void testCaseConstantBehaviorIsSyntaxIndependent() {
 53         CoreOp.FuncOp ruleExpression = lower("caseConstantRuleExpression");
 54         CoreOp.FuncOp ruleBlock = lower("caseConstantRuleBlock");
 55         CoreOp.FuncOp statement = lower("caseConstantStatement");
 56 
 57         String[] args = {"FOO", "BAR", "BAZ", "OTHER"};
 58 
 59         for (String arg : args) {
 60             Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), ruleBlock, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
 61             Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), statement, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
 62         }
 63     }
 64 
 65     @Reflect
 66     public static String caseConstantRuleExpression(String r) {
 67         String s = "";
 68         switch (r) {
 69             case "FOO" -> s += "BAR";
 70             case "BAR" -> s += "BAZ";
 71             case "BAZ" -> s += "FOO";
 72             default -> s += "else";
 73         }
 74         return s;
 75     }
 76 
 77     @Reflect
 78     public static String caseConstantRuleBlock(String r) {
 79         String s = "";
 80         switch (r) {
 81             case "FOO" -> {
 82                 s += "BAR";
 83             }
 84             case "BAR" -> {
 85                 s += "BAZ";
 86             }
 87             case "BAZ" -> {
 88                 s += "FOO";
 89             }
 90             default -> {
 91                 s += "else";
 92             }
 93         }
 94         return s;
 95     }
 96 
 97     @Reflect
 98     private static String caseConstantStatement(String s) {
 99         String r = "";
100         switch (s) {
101             case "FOO":
102                 r += "BAR";
103                 break;
104             case "BAR":
105                 r += "BAZ";
106                 break;
107             case "BAZ":
108                 r += "FOO";
109                 break;
110             default:
111                 r += "else";
112         }
113         return r;
114     }
115 
116     @Test
117     void testCaseConstantMultiLabels() {
118         CoreOp.FuncOp lmodel = lower("caseConstantMultiLabels");
119         char[] args = {'a', 'e', 'i', 'o', 'u', 'j', 'p', 'g'};
120         for (char arg : args) {
121             Assertions.assertEquals(caseConstantMultiLabels(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
122         }
123     }
124 
125     @Reflect
126     private static String caseConstantMultiLabels(char c) {
127         String r = "";
128         switch (Character.toLowerCase(c)) {
129             case 'a', 'e', 'i', 'o', 'u':
130                 r += "vowel";
131                 break;
132             default:
133                 r += "consonant";
134         }
135         return r;
136     }
137 
138     @Test
139     void testCasePatternMultiLabel() {
140         CoreOp.FuncOp lmodel = lower("casePatternMultiLabel");
141         Object[] args = {(byte) 1, (short) 2, 'A', 3, 4L, 5f, 6d, true, "str"};
142         for (Object arg : args) {
143             Assertions.assertEquals(casePatternMultiLabel(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
144         }
145     }
146 
147     @Reflect
148     private static String casePatternMultiLabel(Object o) {
149         String s = null;
150         switch (o) {
151             case Integer _, Long _, Character _, Byte _, Short _-> s = "integral type";
152             default -> s = "non integral type";
153         };
154         return s;
155     }
156 
157     @Test
158     void testCasePatternGuardedMultiLabel() {
159         CoreOp.FuncOp lmodel = lower("casePatternGuardedMultiLabel");
160         Object[] args = {(byte) -1, (short) 2, 'A', -3, 4L, -5f, 6d, true, "str"};
161         for (Object arg : args) {
162             Assertions.assertEquals(casePatternGuardedMultiLabel(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
163         }
164     }
165 
166     @Reflect
167     private static String casePatternGuardedMultiLabel(Object o) {
168         String s = null;
169         switch (o) {
170             case Integer _, Long _, Byte _, Short _ when ((Number)o).intValue() > 0 -> s = "integral type";
171             default -> s = "non integral type";
172         };
173         return s;
174     }
175 
176     @Test
177     void testCaseConstantThrow() {
178         CoreOp.FuncOp lmodel = lower("caseConstantThrow");
179 
180         Assertions.assertThrows(IllegalArgumentException.class, () -> Interpreter.invoke(MethodHandles.lookup(), lmodel, 8));
181 
182         int[] args = {9, 10};
183         for (int arg : args) {
184             Assertions.assertEquals(caseConstantThrow(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
185         }
186     }
187 
188     @Reflect
189     private static String caseConstantThrow(Integer i) {
190         String r = "";
191         switch (i) {
192             case 8 -> throw new IllegalArgumentException();
193             case 9 -> r += "Nine";
194             default -> r += "An integer";
195         }
196         return r;
197     }
198 
199     @Test
200     void testCaseConstantNullLabel() {
201         CoreOp.FuncOp lmodel = lower("caseConstantNullLabel");
202         String[] args = {null, "non null"};
203         for (String arg : args) {
204             Assertions.assertEquals(caseConstantNullLabel(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
205         }
206     }
207 
208     @Reflect
209     private static String caseConstantNullLabel(String s) {
210         String r = "";
211         switch (s) {
212             case null -> r += "null";
213             default -> r += "non null";
214         }
215         return r;
216     }
217 
218     @Test
219     void testCaseConstantFallThrough() {
220         CoreOp.FuncOp lmodel = lower("caseConstantFallThrough");
221         char[] args = {'A', 'B', 'C'};
222         for (char arg : args) {
223             Assertions.assertEquals(caseConstantFallThrough(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
224         }
225     }
226 
227     @Reflect
228     private static String caseConstantFallThrough(char c) {
229         String r = "";
230         switch (c) {
231             case 'A':
232             case 'B':
233                 r += "A or B";
234                 break;
235             default:
236                 r += "Neither A nor B";
237         }
238         return r;
239     }
240 
241     @Test
242     void testCaseConstantNullAndDefault() {
243         CoreOp.FuncOp lmodel = lower("caseConstantNullAndDefault");
244         String[] args = { "abc", "hello", null };
245         for (String arg : args) {
246             Assertions.assertEquals(caseConstantNullAndDefault(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
247         }
248     }
249 
250     @Reflect
251     private static String caseConstantNullAndDefault(String s) {
252         String res = null;
253         switch (s) {
254             case "abc" -> res = "alphabet";
255             case null, default -> res = "null or default";
256         };
257         return res;
258     }
259 
260     @Test
261     void testOnlyDefault() {
262         CoreOp.FuncOp lmodel = lower("onlyDefault");
263         String[] args = {"A", "X"};
264         for (String arg : args) {
265             Assertions.assertEquals(onlyDefault(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
266         }
267     }
268 
269     @Reflect
270     static String onlyDefault(String s) {
271         String res = null;
272         switch (s) {
273             default : res = "A";
274         };
275         return res;
276     }
277 
278     @Test
279     void testCaseConstantEnum() {
280         CoreOp.FuncOp lmodel = lower("caseConstantEnum");
281         for (Day day : Day.values()) {
282             Assertions.assertEquals(caseConstantEnum(day), Interpreter.invoke(MethodHandles.lookup(), lmodel, day));
283         }
284     }
285 
286     enum Day {
287         MON, TUE, WED, THU, FRI, SAT, SUN
288     }
289     @Reflect
290     private static String caseConstantEnum(Day d) {
291         String r = "";
292         switch (d) {
293             case MON, FRI, SUN -> r += 6;
294             case TUE -> r += 7;
295             case THU, SAT -> r += 8;
296             case WED -> r += 9;
297         }
298         return r;
299     }
300 
301     @Test
302     void testCaseConstantEnumWithDefaultFallThrough() {
303         CoreOp.FuncOp lmodel = lower("caseConstantEnumWithDefaultFallThrough");
304         for (Day day : Day.values()) {
305             Assertions.assertEquals(caseConstantEnumWithDefaultFallThrough(day), Interpreter.invoke(MethodHandles.lookup(), lmodel, day));
306         }
307     }
308 
309     @Reflect
310     private static int caseConstantEnumWithDefaultFallThrough(Day d) {
311         switch (d) {
312             case MON, FRI, SUN: return 0;
313             case TUE:
314             default:
315             case WED: return 1;
316         }
317     }
318 
319     @Test
320     void testCaseConstantOtherKindsOfExpr() {
321         CoreOp.FuncOp lmodel = lower("caseConstantOtherKindsOfExpr");
322         for (int i = 0; i < 14; i++) {
323             Assertions.assertEquals(caseConstantOtherKindsOfExpr(i), Interpreter.invoke(MethodHandles.lookup(), lmodel, i));
324         }
325     }
326 
327     static class Constants {
328         static final int c1 = 12;
329     }
330     @Reflect
331     private static String caseConstantOtherKindsOfExpr(int i) {
332         String r = "";
333         final int eleven = 11;
334         switch (i) {
335             case 1 & 0xF -> r += 1;
336             case 4>>1 -> r += "2";
337             case (int) 3L -> r += 3;
338             case 2<<1 -> r += 4;
339             case 10 / 2 -> r += 5;
340             case 12 - 6 -> r += 6;
341             case 3 + 4 -> r += 7;
342             case 2 * 2 * 2 -> r += 8;
343             case 8 | 1 -> r += 9;
344             case (10) -> r += 10;
345             case eleven -> r += 11;
346             case Constants.c1 -> r += Constants.c1;
347             case 1 > 0 ? 13 : 133 -> r += 13;
348             default -> r += "an int";
349         }
350         return r;
351     }
352 
353     @Test
354     void testCaseConstantConv() {
355         CoreOp.FuncOp lmodel = lower("caseConstantConv");
356         for (short i = 1; i < 5; i++) {
357             Assertions.assertEquals(caseConstantConv(i), Interpreter.invoke(MethodHandles.lookup(), lmodel, i));
358         }
359     }
360 
361     @Reflect
362     static String caseConstantConv(short a) {
363         final short s = 1;
364         final byte b = 2;
365         String r = "";
366         switch (a) {
367             case s -> r += "one"; // identity, short -> short
368             case b -> r += "two"; // widening primitive conversion, byte -> short
369             case 3 -> r += "three"; // narrowing primitive conversion, int -> short
370             default -> r += "else";
371         }
372         return r;
373     }
374 
375     @Test
376     void testCaseConstantConv2() {
377         CoreOp.FuncOp lmodel = lower("caseConstantConv2");
378         Byte[] args = {1, 2, 3};
379         for (Byte arg : args) {
380             Assertions.assertEquals(caseConstantConv2(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
381         }
382     }
383 
384     @Reflect
385     static String caseConstantConv2(Byte a) {
386         final byte b = 2;
387         String r = "";
388         switch (a) {
389             case 1 -> r+= "one"; // narrowing primitive conversion followed by a boxing conversion, int -> bye -> Byte
390             case b -> r+= "two"; // boxing, byte -> Byte
391             default -> r+= "default";
392         }
393         return r;
394     }
395 
396     @Test
397     void testNonEnhancedSwStatNoDefault() {
398         CoreOp.FuncOp lmodel = lower("nonEnhancedSwStatNoDefault");
399         for (int i = 1; i < 4; i++) {
400             Assertions.assertEquals(nonEnhancedSwStatNoDefault(i), Interpreter.invoke(MethodHandles.lookup(), lmodel, i));
401         }
402     }
403 
404     @Reflect
405     static String nonEnhancedSwStatNoDefault(int a) {
406         String r = "";
407         switch (a) {
408             case 1 -> r += "1";
409             case 2 -> r += 2;
410         }
411         return r;
412     }
413 
414     // no reason to test enhanced switch statement that has no default
415     // because we can't test for MatchException without separate compilation
416 
417     @Test
418     void testEnhancedSwStatUnconditionalPattern() {
419         CoreOp.FuncOp lmodel = lower("enhancedSwStatUnconditionalPattern");
420         String[] args = {"A", "B"};
421         for (String arg : args) {
422             Assertions.assertEquals(enhancedSwStatUnconditionalPattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
423         }
424     }
425 
426     @Reflect
427     static String enhancedSwStatUnconditionalPattern(String s) {
428         String r = "";
429         switch (s) {
430             case "A" -> r += "A";
431             case Object o -> r += "obj";
432         }
433         return r;
434     }
435 
436     @Test
437     void testCasePatternBehaviorIsSyntaxIndependent() {
438         CoreOp.FuncOp ruleExpression = lower("casePatternRuleExpression");
439         CoreOp.FuncOp ruleBlock = lower("casePatternRuleBlock");
440         CoreOp.FuncOp statement = lower("casePatternStatement");
441 
442         Object[] args = {1, "2", 3L};
443 
444         for (Object arg : args) {
445             Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), ruleBlock, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
446             Assertions.assertEquals(Interpreter.invoke(MethodHandles.lookup(), statement, arg), Interpreter.invoke(MethodHandles.lookup(), ruleExpression, arg));
447         }
448     }
449 
450     @Reflect
451     private static String casePatternRuleExpression(Object o) {
452         String r = "";
453         switch (o) {
454             case Integer i -> r += "integer";
455             case String s -> r+= "string";
456             default -> r+= "else";
457         }
458         return r;
459     }
460 
461     @Reflect
462     private static String casePatternRuleBlock(Object o) {
463         String r = "";
464         switch (o) {
465             case Integer i -> {
466                 r += "integer";
467             }
468             case String s -> {
469                 r += "string";
470             }
471             default -> {
472                 r += "else";
473             }
474         }
475         return r;
476     }
477 
478     @Reflect
479     private static String casePatternStatement(Object o) {
480         String r = "";
481         switch (o) {
482             case Integer i:
483                 r += "integer";
484                 break;
485             case String s:
486                 r += "string";
487                 break;
488             default:
489                 r += "else";
490         }
491         return r;
492     }
493 
494     @Test
495     void testCasePatternThrow() {
496         CoreOp.FuncOp lmodel = lower("casePatternThrow");
497 
498         Object[] args = {Byte.MAX_VALUE, Short.MIN_VALUE, 0, 1L, 11f, 22d};
499         for (Object arg : args) {
500             Assertions.assertThrows(IllegalArgumentException.class, () -> Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
501         }
502 
503         Object[] args2 = {"abc", List.of()};
504         for (Object arg : args2) {
505             Assertions.assertEquals(casePatternThrow(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
506         }
507     }
508 
509     @Reflect
510     private static String casePatternThrow(Object o) {
511         String r = "";
512         switch (o) {
513             case Number n -> throw new IllegalArgumentException();
514             case String s -> r += "a string";
515             default -> r += o.getClass().getName();
516         }
517         return r;
518     }
519 
520     // @@@ when multi patterns is supported, we will test it
521 
522     @Test
523     void testCasePatternWithCaseConstant() {
524         CoreOp.FuncOp lmodel = lower("casePatternWithCaseConstant");
525         int[] args = {42, 43, -44, 0};
526         for (int arg : args) {
527             Assertions.assertEquals(casePatternWithCaseConstant(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
528         }
529     }
530 
531     @Reflect
532     static String casePatternWithCaseConstant(Integer a) {
533         String r = "";
534         switch (a) {
535             case 42 -> r += "forty two";
536             // @@@ case int will not match, because of the way InstanceOfOp is interpreted
537             case Integer i when i > 0 -> r += "positive int";
538             case Integer i when i < 0 -> r += "negative int";
539             default -> r += "zero";
540         }
541         return r;
542     }
543 
544     @Test
545     void testCaseTypePattern() {
546         CoreOp.FuncOp lmodel = lower("caseTypePattern");
547         Object[] args = {"str", new ArrayList<>(), new int[]{}, new Stack[][]{}, new Collection[][][]{}, 8, 'x'};
548         for (Object arg : args) {
549             Assertions.assertEquals(caseTypePattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
550         }
551     }
552 
553     @Reflect
554     static String caseTypePattern(Object o) {
555         String r = "";
556         switch (o) {
557             case String _ -> r+= "String"; // class
558             case RandomAccess _ -> r+= "RandomAccess"; // interface
559             case int[] _ -> r+= "int[]"; // array primitive
560             case Stack[][] _ -> r+= "Stack[][]"; // array class
561             case Collection[][][] _ -> r+= "Collection[][][]"; // array interface
562             case final Number n -> r+= "Number"; // final modifier
563             default -> r+= "something else";
564         }
565         return r;
566     }
567 
568     @Test
569     void testCaseRecordPattern() {
570         // @@@ new R(null) must match the pattern R(Number c), but it doesn't
571         // @@@ test with generic record
572         CoreOp.FuncOp lmodel = lower("caseRecordPattern");
573         Object[] args = {new R(8), new R(1.0), new R(2L), "abc"};
574         for (Object arg : args) {
575             Assertions.assertEquals(caseRecordPattern(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
576         }
577     }
578 
579     record R(Number n) {}
580     @Reflect
581     static String caseRecordPattern(Object o) {
582         String r = "";
583         switch (o) {
584             case R(Number n) -> r += "R(_)";
585             default -> r+= "else";
586         }
587         return r;
588     }
589 
590     @Test
591     void testCasePatternGuard() {
592         CoreOp.FuncOp lmodel = lower("casePatternGuard");
593         Object[] args = {"c++", "java", new R(8), new R(2L), new R(3f), new R(4.0)};
594         for (Object arg : args) {
595             Assertions.assertEquals(casePatternGuard(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
596         }
597     }
598 
599     @Reflect
600     static String casePatternGuard(Object obj) {
601         String r = "";
602         switch (obj) {
603             case String s when s.length() > 3 -> r += "str with length > %d".formatted(s.length());
604             case R(Number n) when n.getClass().equals(Double.class) -> r += "R(Double)";
605             default -> r += "else";
606         }
607         return r;
608     }
609 
610     @Test
611     void testDefaultCaseNotTheLast() {
612         CoreOp.FuncOp lmodel = lower("defaultCaseNotTheLast");
613         String[] args = {"something", "M", "A"};
614         for (String arg : args) {
615             Assertions.assertEquals(defaultCaseNotTheLast(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
616         }
617     }
618 
619     @Reflect
620     static String defaultCaseNotTheLast(String s) {
621         String r = "";
622         switch (s) {
623             default -> r += "else";
624             case "M" -> r += "Mow";
625             case "A" -> r += "Aow";
626         }
627         return r;
628     }
629 
630     @Reflect
631     static String caseConstantPrimitiveWrapperSelector(Integer i) {
632         String r = "";
633         switch (i) {
634             case 1 -> r += "one";
635             case 2, 3 -> r += "two or three";
636             default -> r += "else";
637         };
638         return r;
639     }
640 
641     @Test
642     void testCaseConstantPrimitiveWrapperSelector() {
643         CoreOp.FuncOp lf = lower("caseConstantPrimitiveWrapperSelector");
644         Integer[] args = {1, 2, 3, 4};
645         for (Integer a : args) {
646             Assertions.assertEquals(caseConstantPrimitiveWrapperSelector(a),
647                     Interpreter.invoke(MethodHandles.lookup(), lf, a));
648         }
649     }
650 
651     @Reflect
652     static String constantLabelCasted(int i) {
653         String r = "";
654         switch (i) {
655             case (byte) 1 -> r += "one";
656             default -> r += "not one";
657         };
658         return r;
659     }
660 
661     @Test
662     void testConstantLabelCasted() {
663         CoreOp.FuncOp lf = lower("constantLabelCasted");
664         int[] args = {-1, 1};
665         for (int a : args) {
666             Assertions.assertEquals(constantLabelCasted(a), Interpreter.invoke(MethodHandles.lookup(), lf, a));
667         }
668     }
669 
670     @Reflect
671     static String caseConstantStringLiteral(String s) {
672         String r = "";
673         switch (s) {
674             case "1" -> r += "one";
675             case "2", "3" -> r+= "two or three";
676             default -> r += "else";
677         };
678         return r;
679     }
680 
681     @Test
682     void testCeaseConstantStringLiteral() {
683         CoreOp.FuncOp lf = lower("caseConstantStringLiteral");
684         String[] args = {"1", "2", "3", ""};
685         for (String a : args) {
686             Assertions.assertEquals(caseConstantStringLiteral(a), Interpreter.invoke(MethodHandles.lookup(), lf, a));
687         }
688     }
689 
690     @Test
691     void testTryAndSwitch() {
692         CoreOp.FuncOp lmodel = lower("tryAndSwitch");
693         String[] args = {"A", "B"};
694         for (String arg : args) {
695             Assertions.assertEquals(tryAndSwitch(arg), Interpreter.invoke(MethodHandles.lookup(), lmodel, arg));
696         }
697     }
698 
699     @Reflect
700     private static List<String> tryAndSwitch(String s) {
701         List<String> r = new ArrayList<>();
702         try {
703             switch (s) {
704                 case "A":
705                     r.add("A");
706                     return r;
707                 default:
708                     r.add("B");
709             }
710         } finally {
711             r.add("finally");
712         }
713         return r;
714     }
715 
716     @Reflect
717     private static int caseReassignVar(int sel) {
718         switch (sel) {
719             case 0:
720                 int i = 1;
721                 return i;
722             case 1:
723                 i = 2;
724                 return i;
725             default:
726                 return -1;
727         }
728     }
729 
730     @Reflect
731     private static int caseReassignVarFallThrough(int sel) {
732         switch (sel) {
733             case 0:
734                 int i = 1;
735             case 1:
736                 i = 2;
737                 return i;
738             default:
739                 return -1;
740         }
741     }
742 
743     @Reflect
744     private static int caseReassignVarNested(int sel) {
745         switch (sel) {
746             case 0:
747                 int i = 1;
748                 return i;
749             case 1:
750                 int j = (i = 2);
751                 return i + j;
752             default:
753                 return -1;
754         }
755     }
756 
757     @Reflect
758     private static int caseReassignVarNestedBlock(int sel) {
759         switch (sel) {
760             case 0:
761                 int i = 1;
762                 return i;
763             case 1:
764                 {
765                     i = 2;
766                 }
767                 return i;
768             default:
769                 return -1;
770         }
771     }
772 
773     @Reflect
774     private static int caseReassignVarExpression(int sel) {
775         switch (sel) {
776             case 0:
777                 int i = 1;
778                 return i;
779             case 1:
780                 return i = 2;
781             default:
782                 return -1;
783         }
784     }
785 
786     @Test
787     void testCaseReassignVar() {
788         CoreOp.FuncOp lfrv = lower("caseReassignVar");
789         CoreOp.FuncOp lfrvft = lower("caseReassignVarFallThrough");
790         CoreOp.FuncOp lfrvn = lower("caseReassignVarNested");
791         CoreOp.FuncOp lfrvnb = lower("caseReassignVarNestedBlock");
792         CoreOp.FuncOp lfrve = lower("caseReassignVarExpression");
793         int[] args = {0, 1, 2};
794         for (int a : args) {
795             Assertions.assertEquals(caseReassignVar(a), Interpreter.invoke(MethodHandles.lookup(), lfrv, a));
796             Assertions.assertEquals(caseReassignVarFallThrough(a), Interpreter.invoke(MethodHandles.lookup(), lfrvft, a));
797             Assertions.assertEquals(caseReassignVarNested(a), Interpreter.invoke(MethodHandles.lookup(), lfrvn, a));
798             Assertions.assertEquals(caseReassignVarNestedBlock(a), Interpreter.invoke(MethodHandles.lookup(), lfrvnb, a));
799             Assertions.assertEquals(caseReassignVarExpression(a), Interpreter.invoke(MethodHandles.lookup(), lfrve, a));
800         }
801     }
802 
803     private static CoreOp.FuncOp lower(String methodName) {
804         return lower(getCodeModel(methodName));
805     }
806 
807     private static CoreOp.FuncOp lower(CoreOp.FuncOp f) {
808         writeModel(f, System.out, OpWriter.LocationOption.DROP_LOCATION);
809 
810         CoreOp.FuncOp lf = f.transform(CodeTransformer.LOWERING_TRANSFORMER);
811         writeModel(lf, System.out, OpWriter.LocationOption.DROP_LOCATION);
812 
813         return lf;
814     }
815 
816     private static void writeModel(CoreOp.FuncOp f, OutputStream os, OpWriter.Option... options) {
817         StringWriter sw = new StringWriter();
818         new OpWriter(sw, options).writeOp(f);
819         try {
820             os.write(sw.toString().getBytes());
821         } catch (IOException e) {
822             throw new RuntimeException(e);
823         }
824     }
825 
826     private static CoreOp.FuncOp getCodeModel(String methodName) {
827         Optional<Method> om = Stream.of(TestSwitchStatementOp.class.getDeclaredMethods())
828                 .filter(m -> m.getName().equals(methodName))
829                 .findFirst();
830 
831         return Op.ofMethod(om.get()).get();
832     }
833 }