| /* |
| * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @bug 8205418 8207229 8207230 8230847 8245786 8247334 8248641 8240658 8246774 8274347 |
| * @summary Test the outcomes from Trees.getScope |
| * @modules jdk.compiler/com.sun.tools.javac.api |
| * jdk.compiler/com.sun.tools.javac.comp |
| * jdk.compiler/com.sun.tools.javac.tree |
| * jdk.compiler/com.sun.tools.javac.util |
| */ |
| |
| import java.io.IOException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.lang.model.element.Element; |
| import javax.tools.JavaCompiler; |
| import javax.tools.SimpleJavaFileObject; |
| import javax.tools.StandardJavaFileManager; |
| import javax.tools.ToolProvider; |
| |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.BlockTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.ConditionalExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.Scope; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.JavacTask; |
| import com.sun.source.util.TaskEvent; |
| import com.sun.source.util.TaskListener; |
| import com.sun.source.util.TreePath; |
| import com.sun.source.util.TreePathScanner; |
| import com.sun.source.util.Trees; |
| import com.sun.tools.javac.api.JavacScope; |
| import com.sun.tools.javac.api.JavacTaskImpl; |
| |
| import com.sun.tools.javac.api.JavacTool; |
| import com.sun.tools.javac.comp.Analyzer; |
| import com.sun.tools.javac.comp.AttrContext; |
| import com.sun.tools.javac.comp.Env; |
| import com.sun.tools.javac.tree.JCTree; |
| import com.sun.tools.javac.tree.JCTree.JCCase; |
| import com.sun.tools.javac.tree.JCTree.JCStatement; |
| import com.sun.tools.javac.util.Context; |
| import com.sun.tools.javac.util.Context.Factory; |
| |
| import static javax.tools.JavaFileObject.Kind.SOURCE; |
| |
| public class TestGetScopeResult { |
| public static void main(String... args) throws IOException { |
| new TestGetScopeResult().run(); |
| new TestGetScopeResult().testAnalyzerDisabled(); |
| new TestGetScopeResult().testVariablesInSwitch(); |
| new TestGetScopeResult().testMemberRefs(); |
| new TestGetScopeResult().testAnnotations(); |
| new TestGetScopeResult().testAnnotationsLazy(); |
| new TestGetScopeResult().testCircular(); |
| new TestGetScopeResult().testRecord(); |
| new TestGetScopeResult().testLocalRecordAnnotation(); |
| new TestGetScopeResult().testRuleCases(); |
| new TestGetScopeResult().testNestedSwitchExpression(); |
| } |
| |
| public void run() throws IOException { |
| String[] simpleLambda = { |
| "s:java.lang.String", |
| "i:Test.I", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| doTest("class Test { void test() { I i = s -> { }; } interface I { public void test(String s); } }", |
| simpleLambda); |
| doTest("class Test { void test() { I i = s -> { }; } interface I { public int test(String s); } }", |
| simpleLambda); |
| doTest("class Test { void test() { I i = s -> { }; } interface I { public String test(String s); } }", |
| simpleLambda); |
| doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public void test(String s); } }", |
| simpleLambda); |
| doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public int test(String s); } }", |
| simpleLambda); |
| doTest("class Test { void test() { I i; inv(s -> { }); } void inv(I i) { } interface I { public String test(String s); } }", |
| simpleLambda); |
| String[] dualLambda = { |
| "s:java.lang.String", |
| "i:Test.I1", |
| "super:java.lang.Object", |
| "this:Test", |
| "s:java.lang.CharSequence", |
| "i:Test.I1", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| 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); } }", |
| dualLambda); |
| 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); } }", |
| dualLambda); |
| String[] brokenType = { |
| "s:<any>", |
| "u:Undefined", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| doTest("class Test { void test() { Undefined u = s -> { }; } }", |
| brokenType); |
| String[] multipleCandidates1 = { |
| "s:<any>", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| 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); } }", |
| multipleCandidates1); |
| String[] multipleCandidates2 = { |
| "s:java.lang.String", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| 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); } }", |
| multipleCandidates2); |
| |
| String[] implicitExplicitConflict1 = { |
| "<error>:t", |
| "s:java.lang.String", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| |
| doTest("class Test { void test() { cand((var s, t) -> \"\"); } void cand(I i) { } interface I { public String test(String s); } }", |
| implicitExplicitConflict1); |
| |
| String[] implicitExplicitConflict2 = { |
| "s:none", |
| "<error>:t", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| |
| doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(I i) { } interface I { public String test(String s); } }", |
| implicitExplicitConflict2); |
| |
| String[] noFunctionInterface = { |
| "s:none", |
| "<error>:t", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| |
| doTest("class Test { void test() { cand((t, var s) -> \"\"); } void cand(String s) { } }", |
| noFunctionInterface); |
| |
| String[] invocationInMethodInvocation = { |
| "d2:java.lang.Double", |
| "d1:java.lang.Double", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| |
| doTest(""" |
| class Test { |
| void test() { test(reduce(0.0, (d1, d2) -> 0)); } |
| void test(int i) {} |
| <T> T reduce(T t, BiFunction<T, T, T> f1) {} |
| static interface BiFunction<R, P, Q> { |
| R apply(P p, Q q); |
| } |
| }""", |
| invocationInMethodInvocation); |
| |
| String[] infer = { |
| "c:java.lang.String", |
| "super:java.lang.Object", |
| "this:Test" |
| }; |
| |
| doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i) { } interface I<T> { public String test(T s); } }", |
| infer); |
| doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i, int j) { } interface I<T> { public void test(T s); } }", |
| infer); |
| doTest("class Test { void test() { cand(\"\", c -> { }); } <T>void cand(T t, I<T> i, int j) { } interface I<T> { public String test(T s); } }", |
| infer); |
| } |
| |
| public void doTest(String code, String... expected) throws IOException { |
| JavaCompiler c = ToolProvider.getSystemJavaCompiler(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return code; |
| } |
| } |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, List.of(new MyFileObject())); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| |
| ((JavacTaskImpl)t).enter(); |
| |
| for (int r = 0; r < 2; r++) { |
| List<String> actual = new ArrayList<>(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { |
| Scope scope = Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody())); |
| actual.addAll(dumpScope(scope)); |
| return super.visitLambdaExpression(node, p); |
| } |
| }.scan(cut, null); |
| |
| List<String> expectedList = List.of(expected); |
| |
| if (!expectedList.equals(actual)) { |
| throw new IllegalStateException("Unexpected scope content: " + actual + "\n" + |
| "expected: " + expectedList); |
| } |
| |
| t.analyze(); |
| } |
| } |
| } |
| |
| void testAnalyzerDisabled() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "class Test {" + |
| " void test() { cand(() -> { System.err.println(); }); }" + |
| " Runnable r = new Runnable() { public void test() { System.err.println(); } };" + |
| " void cand(Runnable r) { }" + |
| "}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, List.of("-XDfind=lambda"), null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| TestAnalyzer analyzer = (TestAnalyzer) TestAnalyzer.instance(ctx); |
| |
| if (!analyzer.analyzeCalled) { |
| throw new IllegalStateException("Analyzer didn't run!"); |
| } |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { |
| analyzer.analyzeCalled = false; |
| Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getBody())); |
| if (analyzer.analyzeCalled) { |
| throw new IllegalStateException("Analyzer was run during getScope!"); |
| } |
| return super.visitLambdaExpression(node, p); |
| } |
| |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| if (node.getInitializer() != null) { |
| analyzer.analyzeCalled = false; |
| TreePath tp = new TreePath(getCurrentPath(), node.getInitializer()); |
| Trees.instance(t).getScope(tp); |
| if (analyzer.analyzeCalled) { |
| throw new IllegalStateException("Analyzer was run during getScope!"); |
| } |
| } |
| return super.visitVariable(node, p); |
| } |
| }.scan(cut, null); |
| } |
| } |
| |
| private static final class TestAnalyzer extends Analyzer { |
| |
| public static void preRegister(Context context) { |
| context.put(analyzerKey, (Factory<Analyzer>) ctx -> new TestAnalyzer(ctx)); |
| } |
| |
| private boolean analyzeCalled; |
| |
| public TestAnalyzer(Context context) { |
| super(context); |
| } |
| |
| @Override |
| protected void analyze(JCStatement statement, Env<AttrContext> env) { |
| analyzeCalled = true; |
| super.analyze(statement, env); |
| } |
| } |
| |
| void testVariablesInSwitch() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "class Test {" + |
| " void test() {\n" + |
| " E e = E.A;\n" + |
| " Object o = E.A;\n" + |
| " switch (e) {\n" + |
| " case A:\n" + |
| " return;\n" + |
| " case B:\n" + |
| " test();\n" + |
| " E ee = null;\n" + |
| " break;\n" + |
| " }\n" + |
| " }\n" + |
| " enum E {A, B}\n" + |
| "}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitMethodInvocation(MethodInvocationTree node, Void p) { |
| Trees.instance(t).getScope(getCurrentPath()); |
| return super.visitMethodInvocation(node, p); |
| } |
| }.scan(cut, null); |
| } |
| } |
| |
| void testMemberRefs() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "class Test {" + |
| " void test() {\n" + |
| " Test t = this;\n" + |
| " Runnable r1 = t::test;\n" + |
| " Runnable r2 = true ? t::test : t::test;\n" + |
| " c(t::test);\n" + |
| " c(true ? t::test : t::test);\n" + |
| " }\n" + |
| " void c(Runnable r) {}\n" + |
| "}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitConditionalExpression(ConditionalExpressionTree node, Void p) { |
| Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getCondition())); |
| return super.visitConditionalExpression(node, p); |
| } |
| |
| @Override |
| public Void visitBlock(BlockTree node, Void p) { |
| Trees.instance(t).getScope(getCurrentPath()); |
| return super.visitBlock(node, p); |
| } |
| }.scan(cut, null); |
| } |
| } |
| |
| void testAnnotations() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "class Test {" + |
| " void test() {\n" + |
| " new Object() {\n" + |
| " @A\n" + |
| " public String t() { return null; }\n" + |
| " };\n" + |
| " }\n" + |
| " @interface A {}\n" + |
| "}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitIdentifier(IdentifierTree node, Void p) { |
| if (node.getName().contentEquals("A")) { |
| Trees.instance(t).getScope(getCurrentPath()); |
| } |
| return super.visitIdentifier(node, p); |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree node, Void p) { |
| super.visitMethod(node, p); |
| if (node.getReturnType() != null) { |
| Trees.instance(t).getScope(new TreePath(getCurrentPath(), node.getReturnType())); |
| } |
| return null; |
| } |
| }.scan(cut, null); |
| } |
| } |
| |
| void testAnnotationsLazy() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "import java.lang.annotation.*;\n" + |
| "\n" + |
| "class ClassA {\n" + |
| " Object o = ClassB.lcv;\n" + |
| "}\n" + |
| "\n" + |
| "class ClassB {\n" + |
| " static final String[] lcv = new @TA String[0];\n" + |
| "}\n" + |
| "\n" + |
| "class ClassC {\n" + |
| " static final Object o = (@TA Object) null;\n" + |
| "}\n" + |
| "\n" + |
| "@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})\n" + |
| "@interface TA {}\n"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| t.addTaskListener(new TaskListener() { |
| @Override |
| public void finished(TaskEvent e) { |
| if (e.getKind() == TaskEvent.Kind.ANALYZE) { |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void scan(Tree tree, Void p) { |
| if (tree != null) { |
| Trees.instance(t).getScope(new TreePath(getCurrentPath(), tree)); |
| } |
| return super.scan(tree, p); |
| } |
| }.scan(Trees.instance(t).getPath(e.getTypeElement()), null); |
| } |
| } |
| }); |
| |
| t.call(); |
| } |
| } |
| |
| void testCircular() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "class Test extends Test {" + |
| " {\n" + |
| " int i;\n" + |
| " }\n" + |
| "}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitBlock(BlockTree node, Void p) { |
| Trees.instance(t).getScope(getCurrentPath()); |
| return super.visitBlock(node, p); |
| } |
| }.scan(cut, null); |
| } |
| } |
| |
| void testRecord() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return "record Test<T>(int mark) {}"; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| List<String> actual = new ArrayList<>(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitClass(ClassTree node, Void p) { |
| Scope scope = Trees.instance(t).getScope(getCurrentPath()); |
| actual.addAll(dumpScope(scope)); |
| return super.visitClass(node, p); |
| } |
| }.scan(cut, null); |
| |
| List<String> expected = List.of( |
| "super:java.lang.Record", |
| "this:Test<T>", |
| "T:T" |
| ); |
| |
| if (!expected.equals(actual)) { |
| throw new AssertionError("Unexpected Scope content: " + actual); |
| } |
| } |
| } |
| |
| void testLocalRecordAnnotation() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| class Variant { |
| final String code; |
| final List<List<String>> expectedScopeContent; |
| public Variant(String code, List<List<String>> expectedScopeContent) { |
| this.code = code; |
| this.expectedScopeContent = expectedScopeContent; |
| } |
| } |
| Variant[] variants = new Variant[] { |
| new Variant(""" |
| class Test { |
| void t() { |
| record R(@Annotation int i) { |
| void stop () {} |
| } |
| } |
| } |
| @interface Annotation {} |
| """, |
| List.of( |
| List.of("super:java.lang.Object", "this:Test"), |
| List.of("super:java.lang.Object", "this:Test") |
| )), |
| new Variant(""" |
| record Test(@Annotation int i) {} |
| @interface Annotation {} |
| """, |
| List.of( |
| List.of("i:int", "super:java.lang.Record", "this:Test"), |
| List.of("super:java.lang.Record", "this:Test") |
| )) |
| }; |
| for (Variant currentVariant : variants) { |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return currentVariant.code; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| List<List<String>> actual = new ArrayList<>(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitAnnotation(AnnotationTree node, Void p) { |
| Scope scope = Trees.instance(t).getScope(getCurrentPath()); |
| actual.add(dumpScope(scope)); |
| return super.visitAnnotation(node, p); |
| } |
| }.scan(cut, null); |
| |
| if (!currentVariant.expectedScopeContent.equals(actual)) { |
| throw new AssertionError("Unexpected Scope content: " + actual); |
| } |
| } |
| } |
| } |
| |
| void testRuleCases() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| String code = """ |
| class Test { |
| void t(int i) { |
| long local; |
| System.err.println(switch (i) { |
| case 0 -> { |
| String var; |
| int scopeHere; |
| yield ""; |
| } |
| default -> { |
| String var; |
| int scopeHere; |
| yield ""; |
| } |
| }); |
| switch (i) { |
| case 0 -> { |
| String var; |
| int scopeHere; |
| } |
| default -> { |
| String var; |
| int scopeHere; |
| } |
| }; |
| switch (i) { |
| case 0: { |
| int checkTree; |
| } |
| } |
| } |
| } |
| """; |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return code; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| List<List<String>> actual = new ArrayList<>(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| if (node.getName().contentEquals("scopeHere")) { |
| Scope scope = Trees.instance(t).getScope(getCurrentPath()); |
| actual.add(dumpScope(scope)); |
| JCTree body = getCaseBody(scope); |
| if (body == null) { |
| throw new AssertionError("Unexpected null body."); |
| } |
| } else if (node.getName().contentEquals("checkTree")) { |
| Scope scope = Trees.instance(t).getScope(getCurrentPath()); |
| JCTree body = getCaseBody(scope); |
| if (body != null) { |
| throw new AssertionError("Unexpected body tree: " + body); |
| } |
| } |
| return super.visitVariable(node, p); |
| } |
| JCTree getCaseBody(Scope scope) { |
| return ((JCCase) ((JavacScope) scope).getEnv().next.next.tree).body; |
| } |
| }.scan(cut, null); |
| |
| List<List<String>> expected = |
| Collections.nCopies(4, |
| List.of("scopeHere:int", |
| "var:java.lang.String", |
| "local:long", |
| "i:int", |
| "super:java.lang.Object", |
| "this:Test" |
| )); |
| |
| if (!expected.equals(actual)) { |
| throw new AssertionError("Unexpected Scope content: " + actual); |
| } |
| } |
| } |
| |
| void testNestedSwitchExpression() throws IOException { |
| JavacTool c = JavacTool.create(); |
| try (StandardJavaFileManager fm = c.getStandardFileManager(null, null, null)) { |
| String code = """ |
| class Test { |
| void t(Object o1, Object o2) { |
| System.err.println(switch (o1) { |
| case String s -> switch (j) { |
| case Integer i -> { |
| int scopeHere; |
| yield ""; |
| } |
| default -> ""; |
| }; |
| default -> ""; |
| }); |
| } |
| } |
| """; |
| class MyFileObject extends SimpleJavaFileObject { |
| MyFileObject() { |
| super(URI.create("myfo:///Test.java"), SOURCE); |
| } |
| @Override |
| public String getCharContent(boolean ignoreEncodingErrors) { |
| return code; |
| } |
| } |
| Context ctx = new Context(); |
| TestAnalyzer.preRegister(ctx); |
| JavacTask t = (JavacTask) c.getTask(null, fm, null, null, null, |
| List.of(new MyFileObject()), ctx); |
| CompilationUnitTree cut = t.parse().iterator().next(); |
| t.analyze(); |
| |
| List<List<String>> actual = new ArrayList<>(); |
| |
| new TreePathScanner<Void, Void>() { |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| if (node.getName().contentEquals("scopeHere")) { |
| Scope scope = Trees.instance(t).getScope(getCurrentPath()); |
| actual.add(dumpScope(scope)); |
| JCTree body = getCaseBody(scope); |
| if (body == null) { |
| throw new AssertionError("Unexpected null body."); |
| } |
| } |
| return super.visitVariable(node, p); |
| } |
| JCTree getCaseBody(Scope scope) { |
| return ((JCCase) ((JavacScope) scope).getEnv().next.next.tree).body; |
| } |
| }.scan(cut, null); |
| |
| List<List<String>> expected = |
| List.of(List.of("scopeHere:int", |
| "i:java.lang.Integer", |
| "s:java.lang.String", |
| "o2:java.lang.Object", |
| "o1:java.lang.Object", |
| "super:java.lang.Object", |
| "this:Test" |
| )); |
| |
| if (!expected.equals(actual)) { |
| throw new AssertionError("Unexpected Scope content: " + actual); |
| } |
| } |
| } |
| |
| private List<String> dumpScope(Scope scope) { |
| List<String> content = new ArrayList<>(); |
| while (scope.getEnclosingClass() != null) { |
| for (Element el : scope.getLocalElements()) { |
| content.add(el.getSimpleName() + ":" +el.asType().toString()); |
| } |
| scope = scope.getEnclosingScope(); |
| } |
| return content; |
| } |
| } |