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