1 /*
   2  * Copyright (c) 2018, 2020, 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 /*
  25  * @test
  26  * @bug 8205418 8207229 8207230 8230847 8245786 8247334 8248641
  27  * @summary Test the outcomes from Trees.getScope
  28  * @modules jdk.compiler/com.sun.tools.javac.api
  29  *          jdk.compiler/com.sun.tools.javac.comp
  30  *          jdk.compiler/com.sun.tools.javac.tree
  31  *          jdk.compiler/com.sun.tools.javac.util
  32  */
  33 
  34 import java.io.IOException;
  35 import java.net.URI;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.List;
  39 
  40 import javax.lang.model.element.Element;
  41 import javax.tools.JavaCompiler;
  42 import javax.tools.SimpleJavaFileObject;
  43 import javax.tools.StandardJavaFileManager;
  44 import javax.tools.ToolProvider;
  45 
  46 import com.sun.source.tree.AnnotationTree;
  47 import com.sun.source.tree.BlockTree;
  48 import com.sun.source.tree.ClassTree;
  49 import com.sun.source.tree.CompilationUnitTree;
  50 import com.sun.source.tree.ConditionalExpressionTree;
  51 import com.sun.source.tree.IdentifierTree;
  52 import com.sun.source.tree.LambdaExpressionTree;
  53 import com.sun.source.tree.MethodInvocationTree;
  54 import com.sun.source.tree.MethodTree;
  55 import com.sun.source.tree.Scope;
  56 import com.sun.source.tree.Tree;
  57 import com.sun.source.tree.VariableTree;
  58 import com.sun.source.util.JavacTask;
  59 import com.sun.source.util.TaskEvent;
  60 import com.sun.source.util.TaskListener;
  61 import com.sun.source.util.TreePath;
  62 import com.sun.source.util.TreePathScanner;
  63 import com.sun.source.util.Trees;
  64 import com.sun.tools.javac.api.JavacScope;
  65 
  66 import com.sun.tools.javac.api.JavacTool;
  67 import com.sun.tools.javac.comp.Analyzer;
  68 import com.sun.tools.javac.comp.AttrContext;
  69 import com.sun.tools.javac.comp.Env;
  70 import com.sun.tools.javac.tree.JCTree;
  71 import com.sun.tools.javac.tree.JCTree.JCCase;
  72 import com.sun.tools.javac.tree.JCTree.JCStatement;
  73 import com.sun.tools.javac.util.Context;
  74 import com.sun.tools.javac.util.Context.Factory;
  75 
  76 import static javax.tools.JavaFileObject.Kind.SOURCE;
  77 
  78 public class TestGetScopeResult {
  79     public static void main(String... args) throws IOException {
  80         new TestGetScopeResult().run();
  81         new TestGetScopeResult().testAnalyzerDisabled();
  82         new TestGetScopeResult().testVariablesInSwitch();
  83         new TestGetScopeResult().testMemberRefs();
  84         new TestGetScopeResult().testAnnotations();
  85         new TestGetScopeResult().testAnnotationsLazy();
  86         new TestGetScopeResult().testCircular();
  87         new TestGetScopeResult().testRecord();
  88         new TestGetScopeResult().testLocalRecordAnnotation();
  89         new TestGetScopeResult().testRuleCases();
  90     }
  91 
  92     public void run() throws IOException {
  93         String[] simpleLambda = {
  94             "s:java.lang.String",
  95             "i:Test.I",
  96             "super:java.lang.Object",
  97             "this:Test"
  98         };
  99         doTest("class Test { void test() { I i = s -> { }; } interface I { public void test(String s); } }",
 100                simpleLambda);
 101         doTest("class Test { void test() { I i = s -> { }; } interface I { public int test(String s); } }",
 102                simpleLambda);
 103         doTest("class Test { void test() { I i = s -> { }; } interface I { public String test(String s); } }",
 104                simpleLambda);
 105         doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public void test(String s); } }",
 106                simpleLambda);
 107         doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public int test(String s); } }",
 108                simpleLambda);
 109         doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public String test(String s); } }",
 110                simpleLambda);
 111         String[] dualLambda = {
 112             "s:java.lang.String",
 113             "i:Test.I1",
 114             "super:java.lang.Object",
 115             "this:Test",
 116             "s:java.lang.CharSequence",
 117             "i:Test.I1",
 118             "super:java.lang.Object",
 119             "this:Test"
 120         };
 121         doTest("class Test { void test() { I1 i; inv(s -> { }, s -> { }); } void inv(I1 i, I2 i) { } interface I1 { public String test(String s); } interface I2 { public void test(CharSequence s); } }",
 122                dualLambda);
 123         doTest("class Test { void test() { I1 i; inv(s -> { }, s -> { }); } void inv(I1 i, I2 i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }",
 124                dualLambda);
 125         String[] brokenType = {
 126             "s:<any>",
 127             "u:Undefined",
 128             "super:java.lang.Object",
 129             "this:Test"
 130         };
 131         doTest("class Test { void test() { Undefined u = s -> { }; } }",
 132                brokenType);
 133         String[] multipleCandidates1 = {
 134             "s:<any>",
 135             "super:java.lang.Object",
 136             "this:Test"
 137         };
 138         doTest("class Test { void test() { cand1(s -> { }); } void cand1(I1 i) { } void cand1(I2 i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }",
 139                multipleCandidates1);
 140         String[] multipleCandidates2 = {
 141             "s:java.lang.String",
 142             "super:java.lang.Object",
 143             "this:Test"
 144         };
 145         doTest("class Test { void test() { cand1(s -> { }); } void cand1(I1 i) { } void cand1(I2 i, int i) { } interface I1 { public String test(String s); } interface I2 { public int test(CharSequence s); } }",
 146                multipleCandidates2);
 147 
 148         String[] implicitExplicitConflict1 = {
 149             ":t",
 150             "s:java.lang.String",
 151             "super:java.lang.Object",
 152             "this:Test"
 153         };
 154 
 155         doTest("class Test { void test() { cand((var s, t) -> \"\"); } void cand(I i) { } interface I { public String test(String s); }  }",
 156                implicitExplicitConflict1);
 157 
 158         String[] implicitExplicitConflict2 = {
 159             "s:none",
 160             ":t",
 161             "super:java.lang.Object",
 162             "this:Test"
 163         };
 164 
 165         doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(I i) { } interface I { public String test(String s); }  }",
 166                implicitExplicitConflict2);
 167 
 168         String[] noFunctionInterface = {
 169             "s:none",
 170             ":t",
 171             "super:java.lang.Object",
 172             "this:Test"
 173         };
 174 
 175         doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(String s) { } }",
 176                noFunctionInterface);
 177 
 178         String[] invocationInMethodInvocation = {
 179             "d2:java.lang.Double",
 180             "d1:java.lang.Double",
 181             "super:java.lang.Object",
 182             "this:Test"
 183         };
 184 
 185         doTest("""
 186                class Test {
 187                    void test() { test(reduce(0.0, (d1, d2) -> 0)); }
 188                    void test(int i) {}
 189                    <T> T reduce(T t, BiFunction<T, T, T> f1) {}
 190                    static interface BiFunction<R, P, Q> {
 191                        R apply(P p, Q q);
 192                    }
 193                }""",
 194                invocationInMethodInvocation);
 195     }
 196 
 197     public void doTest(String code, String... expected) throws IOException {
 198         JavaCompiler c = ToolProvider.getSystemJavaCompiler();
 199         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 200             class MyFileObject extends SimpleJavaFileObject {
 201                 MyFileObject() {
 202                     super(URI.create("myfo:///Test.java"), SOURCE);
 203                 }
 204                 @Override
 205                 public String getCharContent(boolean ignoreEncodingErrors) {
 206                     return code;
 207                 }
 208             }
 209             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, List.of(new MyFileObject()));
 210             CompilationUnitTree cut = t.parse().iterator().next();
 211             t.analyze();
 212 
 213             List<String> actual = new ArrayList<>();
 214 
 215             new TreePathScanner<Void, Void>() {
 216                 @Override
 217                 public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
 218                     Scope scope = Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody()));
 219                     actual.addAll(dumpScope(scope));
 220                     return super.visitLambdaExpression(node, p);
 221                 }
 222             }.scan(cut, null);
 223 
 224             List<String> expectedList = List.of(expected);
 225 
 226             if (!expectedList.equals(actual)) {
 227                 throw new IllegalStateException("Unexpected scope content: " + actual + "\n" +
 228                                                  "expected: " + expectedList);
 229             }
 230         }
 231     }
 232 
 233     void testAnalyzerDisabled() throws IOException {
 234         JavacTool c = JavacTool.create();
 235         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 236             class MyFileObject extends SimpleJavaFileObject {
 237                 MyFileObject() {
 238                     super(URI.create("myfo:///Test.java"), SOURCE);
 239                 }
 240                 @Override
 241                 public String getCharContent(boolean ignoreEncodingErrors) {
 242                     return "class Test {" +
 243                            "    void test() { cand(() -> { System.err.println(); }); }" +
 244                            "    Runnable r = new Runnable() { public void test() { System.err.println(); } };" +
 245                            "    void cand(Runnable r) { }" +
 246                            "}";
 247                 }
 248             }
 249             Context ctx = new Context();
 250             TestAnalyzer.preRegister(ctx);
 251             JavacTask t = (JavacTask) c.getTask(null, fm, null, List.of("-XDfind=lambda"), null,
 252                                                 List.of(new MyFileObject()), ctx);
 253             CompilationUnitTree cut = t.parse().iterator().next();
 254             t.analyze();
 255 
 256             TestAnalyzer analyzer = (TestAnalyzer) TestAnalyzer.instance(ctx);
 257 
 258             if (!analyzer.analyzeCalled) {
 259                 throw new IllegalStateException("Analyzer didn't run!");
 260             }
 261 
 262             new TreePathScanner<Void, Void>() {
 263                 @Override
 264                 public Void visitLambdaExpression(LambdaExpressionTree node, Void p) {
 265                     analyzer.analyzeCalled = false;
 266                     Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody()));
 267                     if (analyzer.analyzeCalled) {
 268                         throw new IllegalStateException("Analyzer was run during getScope!");
 269                     }
 270                     return super.visitLambdaExpression(node, p);
 271                 }
 272 
 273                 @Override
 274                 public Void visitVariable(VariableTree node, Void p) {
 275                     if (node.getInitializer() != null) {
 276                         analyzer.analyzeCalled = false;
 277                         TreePath tp = new TreePath(getCurrentPath(), node.getInitializer());
 278                         Trees.instance(t).getScope(tp);
 279                         if (analyzer.analyzeCalled) {
 280                             throw new IllegalStateException("Analyzer was run during getScope!");
 281                         }
 282                     }
 283                     return super.visitVariable(node, p);
 284                 }
 285             }.scan(cut, null);
 286         }
 287     }
 288 
 289     private static final class TestAnalyzer extends Analyzer {
 290 
 291         public static void preRegister(Context context) {
 292             context.put(analyzerKey, (Factory<Analyzer>) ctx -> new TestAnalyzer(ctx));
 293         }
 294 
 295         private boolean analyzeCalled;
 296 
 297         public TestAnalyzer(Context context) {
 298             super(context);
 299         }
 300 
 301         @Override
 302         protected void analyze(JCStatement statement, Env<AttrContext> env) {
 303             analyzeCalled = true;
 304             super.analyze(statement, env);
 305         }
 306     }
 307 
 308     void testVariablesInSwitch() throws IOException {
 309         JavacTool c = JavacTool.create();
 310         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 311             class MyFileObject extends SimpleJavaFileObject {
 312                 MyFileObject() {
 313                     super(URI.create("myfo:///Test.java"), SOURCE);
 314                 }
 315                 @Override
 316                 public String getCharContent(boolean ignoreEncodingErrors) {
 317                     return "class Test {" +
 318                            "    void test() {\n" +
 319                            "        E e = E.A;\n" +
 320                            "        Object o = E.A;\n" +
 321                            "        switch (e) {\n" +
 322                            "            case A:\n" +
 323                            "                return;\n" +
 324                            "            case B:\n" +
 325                            "                test();\n" +
 326                            "                E ee = null;\n" +
 327                            "                break;\n" +
 328                            "        }\n" +
 329                            "    }\n" +
 330                            "    enum E {A, B}\n" +
 331                            "}";
 332                 }
 333             }
 334             Context ctx = new Context();
 335             TestAnalyzer.preRegister(ctx);
 336             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null,
 337                                                 List.of(new MyFileObject()), ctx);
 338             CompilationUnitTree cut = t.parse().iterator().next();
 339             t.analyze();
 340 
 341             new TreePathScanner<Void, Void>() {
 342                 @Override
 343                 public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
 344                     Trees.instance(t).getScope(getCurrentPath());
 345                     return super.visitMethodInvocation(node, p);
 346                 }
 347             }.scan(cut, null);
 348         }
 349     }
 350 
 351     void testMemberRefs() throws IOException {
 352         JavacTool c = JavacTool.create();
 353         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 354             class MyFileObject extends SimpleJavaFileObject {
 355                 MyFileObject() {
 356                     super(URI.create("myfo:///Test.java"), SOURCE);
 357                 }
 358                 @Override
 359                 public String getCharContent(boolean ignoreEncodingErrors) {
 360                     return "class Test {" +
 361                            "    void test() {\n" +
 362                            "        Test t = this;\n" +
 363                            "        Runnable r1 = t::test;\n" +
 364                            "        Runnable r2 = true ? t::test : t::test;\n" +
 365                            "        c(t::test);\n" +
 366                            "        c(true ? t::test : t::test);\n" +
 367                            "    }\n" +
 368                            "    void c(Runnable r) {}\n" +
 369                            "}";
 370                 }
 371             }
 372             Context ctx = new Context();
 373             TestAnalyzer.preRegister(ctx);
 374             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null,
 375                                                 List.of(new MyFileObject()), ctx);
 376             CompilationUnitTree cut = t.parse().iterator().next();
 377             t.analyze();
 378 
 379             new TreePathScanner<Void, Void>() {
 380                 @Override
 381                 public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) {
 382                     Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getCondition()));
 383                     return super.visitConditionalExpression(node, p);
 384                 }
 385 
 386                 @Override
 387                 public Void visitBlock(BlockTree node, Void p) {
 388                     Trees.instance(t).getScope(getCurrentPath());
 389                     return super.visitBlock(node, p);
 390                 }
 391             }.scan(cut, null);
 392         }
 393     }
 394 
 395     void testAnnotations() throws IOException {
 396         JavacTool c = JavacTool.create();
 397         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 398             class MyFileObject extends SimpleJavaFileObject {
 399                 MyFileObject() {
 400                     super(URI.create("myfo:///Test.java"), SOURCE);
 401                 }
 402                 @Override
 403                 public String getCharContent(boolean ignoreEncodingErrors) {
 404                     return "class Test {" +
 405                            "    void test() {\n" +
 406                            "        new Object() {\n" +
 407                            "            @A\n" +
 408                            "            public String t() { return null; }\n" +
 409                            "        };\n" +
 410                            "    }\n" +
 411                            "    @interface A {}\n" +
 412                            "}";
 413                 }
 414             }
 415             Context ctx = new Context();
 416             TestAnalyzer.preRegister(ctx);
 417             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null,
 418                                                 List.of(new MyFileObject()), ctx);
 419             CompilationUnitTree cut = t.parse().iterator().next();
 420             t.analyze();
 421 
 422             new TreePathScanner<Void, Void>() {
 423                 @Override
 424                 public Void visitIdentifier(IdentifierTree node, Void p) {
 425                     if (node.getName().contentEquals("A")) {
 426                         Trees.instance(t).getScope(getCurrentPath());
 427                     }
 428                     return super.visitIdentifier(node, p);
 429                 }
 430 
 431                 @Override
 432                 public Void visitMethod(MethodTree node, Void p) {
 433                     super.visitMethod(node, p);
 434                     if (node.getReturnType() != null) {
 435                         Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getReturnType()));
 436                     }
 437                     return null;
 438                 }
 439             }.scan(cut, null);
 440         }
 441     }
 442 
 443     void testAnnotationsLazy() throws IOException {
 444         JavacTool c = JavacTool.create();
 445         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 446             class MyFileObject extends SimpleJavaFileObject {
 447                 MyFileObject() {
 448                     super(URI.create("myfo:///Test.java"), SOURCE);
 449                 }
 450                 @Override
 451                 public String getCharContent(boolean ignoreEncodingErrors) {
 452                     return "import java.lang.annotation.*;\n" +
 453                            "\n" +
 454                            "class ClassA {\n" +
 455                            "    Object o = ClassB.lcv;\n" +
 456                            "}\n" +
 457                            "\n" +
 458                            "class ClassB {\n" +
 459                            "    static final String[] lcv = new @TA String[0];\n" +
 460                            "}\n" +
 461                            "\n" +
 462                            "class ClassC {\n" +
 463                            "    static final Object o = (@TA Object) null;\n" +
 464                            "}\n" +
 465                            "\n" +
 466                            "@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})\n" +
 467                            "@interface TA {}\n";
 468                 }
 469             }
 470             Context ctx = new Context();
 471             TestAnalyzer.preRegister(ctx);
 472             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null,
 473                                                 List.of(new MyFileObject()), ctx);
 474             t.addTaskListener(new TaskListener() {
 475                 @Override
 476                 public void finished(TaskEvent e) {
 477                     if (e.getKind() == TaskEvent.Kind.ANALYZE) {
 478                         new TreePathScanner<Void, Void>() {
 479                             @Override
 480                             public Void scan(Tree tree, Void p) {
 481                                 if (tree != null) {
 482                                     Trees.instance(t).getScope(new TreePath(getCurrentPath(), tree));
 483                                 }
 484                                 return super.scan(tree, p);
 485                             }
 486                         }.scan(Trees.instance(t).getPath(e.getTypeElement()), null);
 487                     }
 488                 }
 489             });
 490 
 491             t.call();
 492         }
 493     }
 494 
 495     void testCircular() throws IOException {
 496         JavacTool c = JavacTool.create();
 497         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 498             class MyFileObject extends SimpleJavaFileObject {
 499                 MyFileObject() {
 500                     super(URI.create("myfo:///Test.java"), SOURCE);
 501                 }
 502                 @Override
 503                 public String getCharContent(boolean ignoreEncodingErrors) {
 504                     return "class Test extends Test {" +
 505                            "    {\n" +
 506                            "        int i;\n" +
 507                            "    }\n" +
 508                            "}";
 509                 }
 510             }
 511             Context ctx = new Context();
 512             TestAnalyzer.preRegister(ctx);
 513             JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null,
 514                                                 List.of(new MyFileObject()), ctx);
 515             CompilationUnitTree cut = t.parse().iterator().next();
 516             t.analyze();
 517 
 518             new TreePathScanner<Void, Void>() {
 519                 @Override
 520                 public Void visitBlock(BlockTree node, Void p) {
 521                     Trees.instance(t).getScope(getCurrentPath());
 522                     return super.visitBlock(node, p);
 523                 }
 524             }.scan(cut, null);
 525         }
 526     }
 527 
 528     void testRecord() throws IOException {
 529         JavacTool c = JavacTool.create();
 530         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 531             class MyFileObject extends SimpleJavaFileObject {
 532                 MyFileObject() {
 533                     super(URI.create("myfo:///Test.java"), SOURCE);
 534                 }
 535                 @Override
 536                 public String getCharContent(boolean ignoreEncodingErrors) {
 537                     return "record Test<T>(int mark) {}";
 538                 }
 539             }
 540             Context ctx = new Context();
 541             TestAnalyzer.preRegister(ctx);
 542             List<String> options = List.of("--enable-preview",
 543                                            "-source", System.getProperty("java.specification.version"));
 544             JavacTask t = (JavacTask) c.getTask(null, fm, null, options, null,
 545                                                 List.of(new MyFileObject()), ctx);
 546             CompilationUnitTree cut = t.parse().iterator().next();
 547             t.analyze();
 548 
 549             List<String> actual = new ArrayList<>();
 550 
 551             new TreePathScanner<Void, Void>() {
 552                 @Override
 553                 public Void visitClass(ClassTree node, Void p) {
 554                     Scope scope = Trees.instance(t).getScope(getCurrentPath());
 555                     actual.addAll(dumpScope(scope));
 556                     return super.visitClass(node, p);
 557                 }
 558             }.scan(cut, null);
 559 
 560             List<String> expected = List.of(
 561                     "super:java.lang.Record",
 562                     "this:Test<T>",
 563                     "T:T"
 564             );
 565 
 566             if (!expected.equals(actual)) {
 567                 throw new AssertionError("Unexpected Scope content: " + actual);
 568             }
 569         }
 570     }
 571 
 572     void testLocalRecordAnnotation() throws IOException {
 573         JavacTool c = JavacTool.create();
 574         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 575             class Variant {
 576                 final String code;
 577                 final List<List<String>> expectedScopeContent;
 578                 public Variant(String code, List<List<String>> expectedScopeContent) {
 579                     this.code = code;
 580                     this.expectedScopeContent = expectedScopeContent;
 581                 }
 582             }
 583             Variant[] variants = new Variant[] {
 584                 new Variant("""
 585                             class Test {
 586                                 void t() {
 587                                     record R(@Annotation int i) {
 588                                         void stop () {}
 589                                     }
 590                                 }
 591                             }
 592                             @interface Annotation {}
 593                             """,
 594                             List.of(
 595                                 List.of("super:java.lang.Object", "this:Test"),
 596                                 List.of("super:java.lang.Object", "this:Test")
 597                             )),
 598                 new Variant("""
 599                             record Test(@Annotation int i) {}
 600                             @interface Annotation {}
 601                             """,
 602                             List.of(
 603                                 List.of("i:int", "super:java.lang.Record", "this:Test"),
 604                                 List.of("super:java.lang.Record", "this:Test")
 605                             ))
 606             };
 607             for (Variant currentVariant : variants) {
 608                 class MyFileObject extends SimpleJavaFileObject {
 609                     MyFileObject() {
 610                         super(URI.create("myfo:///Test.java"), SOURCE);
 611                     }
 612                     @Override
 613                     public String getCharContent(boolean ignoreEncodingErrors) {
 614                         return currentVariant.code;
 615                     }
 616                 }
 617                 Context ctx = new Context();
 618                 TestAnalyzer.preRegister(ctx);
 619                 List<String> options = List.of("--enable-preview",
 620                                                "-source", System.getProperty("java.specification.version"));
 621                 JavacTask t = (JavacTask) c.getTask(null, fm, null, options, null,
 622                                                     List.of(new MyFileObject()), ctx);
 623                 CompilationUnitTree cut = t.parse().iterator().next();
 624                 t.analyze();
 625 
 626                 List<List<String>> actual = new ArrayList<>();
 627 
 628                 new TreePathScanner<Void, Void>() {
 629                     @Override
 630                     public Void visitAnnotation(AnnotationTree node, Void p) {
 631                         Scope scope = Trees.instance(t).getScope(getCurrentPath());
 632                         actual.add(dumpScope(scope));
 633                         return super.visitAnnotation(node, p);
 634                     }
 635                 }.scan(cut, null);
 636 
 637                 if (!currentVariant.expectedScopeContent.equals(actual)) {
 638                     throw new AssertionError("Unexpected Scope content: " + actual);
 639                 }
 640             }
 641         }
 642     }
 643 
 644     void testRuleCases() throws IOException {
 645         JavacTool c = JavacTool.create();
 646         try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) {
 647             String code = """
 648                           class Test {
 649                               void t(int i) {
 650                                   long local;
 651                                   System.err.println(switch (i) {
 652                                     case 0 -> {
 653                                         String var;
 654                                         int scopeHere;
 655                                         yield "";
 656                                     }
 657                                     default -> {
 658                                         String var;
 659                                         int scopeHere;
 660                                         yield "";
 661                                     }
 662                                   });
 663                                   switch (i) {
 664                                     case 0 -> {
 665                                         String var;
 666                                         int scopeHere;
 667                                     }
 668                                     default -> {
 669                                         String var;
 670                                         int scopeHere;
 671                                     }
 672                                   };
 673                                   switch (i) {
 674                                     case 0: {
 675                                         int checkTree;
 676                                     }
 677                                   }
 678                               }
 679                           }
 680                           """;
 681             class MyFileObject extends SimpleJavaFileObject {
 682                 MyFileObject() {
 683                     super(URI.create("myfo:///Test.java"), SOURCE);
 684                 }
 685                 @Override
 686                 public String getCharContent(boolean ignoreEncodingErrors) {
 687                     return code;
 688                 }
 689             }
 690             Context ctx = new Context();
 691             TestAnalyzer.preRegister(ctx);
 692             List<String> options = List.of("--enable-preview",
 693                                            "-source", System.getProperty("java.specification.version"));
 694             JavacTask t = (JavacTask) c.getTask(null, fm, null, options, null,
 695                                                 List.of(new MyFileObject()), ctx);
 696             CompilationUnitTree cut = t.parse().iterator().next();
 697             t.analyze();
 698 
 699             List<List<String>> actual = new ArrayList<>();
 700 
 701             new TreePathScanner<Void, Void>() {
 702                 @Override
 703                 public Void visitVariable(VariableTree node, Void p) {
 704                     if (node.getName().contentEquals("scopeHere")) {
 705                         Scope scope = Trees.instance(t).getScope(getCurrentPath());
 706                         actual.add(dumpScope(scope));
 707                     } else if (node.getName().contentEquals("checkTree")) {
 708                         Scope scope = Trees.instance(t).getScope(getCurrentPath());
 709                         JCTree body =
 710                                 ((JCCase) ((JavacScope) scope).getEnv().next.next.tree).body;
 711                         if (body != null) {
 712                             throw new AssertionError("Unexpected body tree: " + body);
 713                         }
 714                     }
 715                     return super.visitVariable(node, p);
 716                 }
 717             }.scan(cut, null);
 718 
 719             List<List<String>> expected =
 720                     Collections.nCopies(4,
 721                                         List.of("scopeHere:int",
 722                                                 "var:java.lang.String",
 723                                                 "local:long",
 724                                                 "i:int",
 725                                                 "super:java.lang.Object",
 726                                                 "this:Test"
 727                                             ));
 728 
 729             if (!expected.equals(actual)) {
 730                 throw new AssertionError("Unexpected Scope content: " + actual);
 731             }
 732         }
 733     }
 734 
 735     private List<String> dumpScope(Scope scope) {
 736         List<String> content = new ArrayList<>();
 737         while (scope.getEnclosingClass() != null) {
 738             for (Element el : scope.getLocalElements()) {
 739                 content.add(el.getSimpleName() + ":" +el.asType().toString());
 740             }
 741             scope = scope.getEnclosingScope();
 742         }
 743         return content;
 744     }
 745 }