From 19be548f0bc53ecac53122e478a37e27ce344118 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Fri, 30 Jul 2021 16:59:09 +0200 Subject: [PATCH] Fixing handling of pattern matching in instanceof in code generator (#3053) --- .../modules/java/source/TreeShims.java | 2 +- .../modules/java/source/save/CasualDiff.java | 22 +++++++ .../transform/ImmutableTreeTranslator.java | 34 ++++++++-- .../api/java/source/gen/InstanceOfTest.java | 62 +++++++++++++++++-- .../refactoring/java/test/RenameTest.java | 31 ++++++++++ 5 files changed, 140 insertions(+), 11 deletions(-) diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java index ed13513e500b..4cb080722615 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/TreeShims.java @@ -256,7 +256,7 @@ public static Tree getBindingPatternType(Tree node) { } @SuppressWarnings("unchecked") - private static RuntimeException throwAny(Throwable t) throws T { + public static RuntimeException throwAny(Throwable t) throws T { throw (T) t; } public static boolean isRecord(Element el) { diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java index 7979b0840bb4..5b879c2e30b8 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/save/CasualDiff.java @@ -132,6 +132,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; import java.util.Map.Entry; @@ -1958,6 +1959,23 @@ protected int diffSwitchExpression(Tree oldT, Tree newT, int[] bounds) { return bounds[1]; } + protected int diffBindingPattern(Tree oldT, Tree newT, int[] bounds) { + VariableTree oldVar = getBindingVariableTree(oldT); + VariableTree newVar = getBindingVariableTree(newT); + + return diffTree((JCTree) oldVar, (JCTree) newVar, bounds); + } + + @NbBundle.Messages("ERR_PatternMatchingInstanceOf=Transformation for pattern matching in instanceof not supported on this version of JDK. Please run on JDK 16 or newer, or install nb-javac.") + public static VariableTree getBindingVariableTree(Tree node) { + try { + Class bpt = Class.forName("com.sun.source.tree.BindingPatternTree"); //NOI18N + return (VariableTree)bpt.getDeclaredMethod("getVariable").invoke(node); //NOI18N + } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + throw TreeShims.throwAny(Exceptions.attachLocalizedMessage(ex, Bundle.ERR_PatternMatchingInstanceOf())); + } + } + protected int diffCase(JCCase oldT, JCCase newT, int[] bounds) { int localPointer = bounds[0]; List oldPatterns = getCasePatterns(oldT); @@ -5730,6 +5748,10 @@ private int diffTreeImpl0(JCTree oldT, JCTree newT, JCTree parent /*used only fo retVal = diffSwitchExpression(oldT, newT, elementBounds); break; } + if(oldT.getKind().toString().equals(TreeShims.BINDING_PATTERN)){ + retVal = diffBindingPattern(oldT, newT, elementBounds); + break; + } String msg = "Diff not implemented: " + ((com.sun.source.tree.Tree)oldT).getKind().toString() + " " + oldT.getClass().getName(); diff --git a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java index 85d1461ebb5c..b490380dba31 100644 --- a/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java +++ b/java/java.source.base/src/org/netbeans/modules/java/source/transform/ImmutableTreeTranslator.java @@ -24,10 +24,10 @@ import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.model.JavacElements; -import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCLambda; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.util.Context; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -48,9 +48,12 @@ import org.netbeans.modules.java.source.builder.TreeFactory; import org.netbeans.modules.java.source.pretty.ImportAnalysis2; import org.netbeans.modules.java.source.query.CommentHandler; +import org.netbeans.modules.java.source.save.CasualDiff; import org.netbeans.modules.java.source.save.ElementOverlay; import static org.netbeans.modules.java.source.save.PositionEstimator.*; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; /** A subclass of Tree.Visitor, this class defines * a general tree translator pattern. Translation proceeds recursively in @@ -119,9 +122,11 @@ public void release() { /** Visitor method: Translate a single node. */ public Tree translate(Tree tree) { - if (tree == null) + if (tree == null) { return null; - else { + } else if (tree.getKind().name().equals(TreeShims.BINDING_PATTERN)) { + return rewriteChildrenBindingPattern(tree); + } else { Tree t = tree.accept(this, null); if (tree2Tag != null && tree != t && tmaker != null) { @@ -1134,9 +1139,13 @@ protected final TypeCastTree rewriteChildren(TypeCastTree tree) { protected final InstanceOfTree rewriteChildren(InstanceOfTree tree) { ExpressionTree expr = (ExpressionTree)translate(tree.getExpression()); - Tree clazz = translateClassRef(tree.getType()); - if (expr!=tree.getExpression() || clazz!=tree.getType()) { - InstanceOfTree n = make.InstanceOf(expr, clazz); + Tree origPattern = TreeShims.getPattern(tree); + if (origPattern == null) { + origPattern = tree.getType(); + } + Tree newPattern = translate(origPattern); + if (expr!=tree.getExpression() || newPattern!=origPattern) { + InstanceOfTree n = make.InstanceOf(expr, newPattern); model.setType(n, model.getType(tree)); copyCommentTo(tree,n); copyPosTo(tree,n); @@ -1405,4 +1414,17 @@ private Tree rewriteChildren(UsesTree tree) { } return tree; } + + private Tree rewriteChildrenBindingPattern(Tree tree) { + VariableTree var = CasualDiff.getBindingVariableTree(tree); //replace with tree.getVariable when javac supported is 16+: + VariableTree newVar = (VariableTree) translate(var); + if (newVar != var) { + Tree n = make.BindingPattern(newVar); + model.setType(n, model.getType(tree)); + copyCommentTo(tree,n); + copyPosTo(tree,n); + tree = n; + } + return tree; + } } diff --git a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/InstanceOfTest.java b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/InstanceOfTest.java index 19cc98df00bb..aff6ea1713a0 100644 --- a/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/InstanceOfTest.java +++ b/java/java.source.base/test/unit/src/org/netbeans/api/java/source/gen/InstanceOfTest.java @@ -21,16 +21,17 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.IfTree; import com.sun.source.tree.InstanceOfTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ParenthesizedTree; +import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import com.sun.tools.javac.tree.JCTree; +import com.sun.source.util.TreeScanner; import java.io.File; import java.io.IOException; import java.util.EnumSet; -import javax.lang.model.SourceVersion; import javax.lang.model.element.Modifier; import org.netbeans.api.java.source.Task; import org.netbeans.api.java.source.JavaSource; @@ -197,11 +198,64 @@ public void run(WorkingCopy workingCopy) throws IOException { assertEquals(golden, res); } + public void testRenamePatternMatchingType() throws Exception { + if (!typeTestPatternAvailable()) + return ; + + testFile = new File(getWorkDir(), "Test.java"); + TestUtilities.copyStringToFile(testFile, + "package hierbas.del.litoral;\n\n" + + "public class Test {\n" + + " public boolean taragui(Object o) {\n" + + " return o instanceof Test t;\n" + + " }\n" + + "}\n" + ); + String golden = + "package hierbas.del.litoral;\n\n" + + "public class Test {\n" + + " public boolean taragui(Object o) {\n" + + " return o2 instanceof Test2 t;\n" + + " }\n" + + "}\n"; + JavaSource src = getJavaSource(testFile); + + Task task = new Task() { + + public void run(WorkingCopy workingCopy) throws IOException { + workingCopy.toPhase(Phase.RESOLVED); + CompilationUnitTree cut = workingCopy.getCompilationUnit(); + TreeMaker make = workingCopy.getTreeMaker(); + new TreeScanner() { + @Override + public Void visitVariable(VariableTree node, Void p) { + if (node.getName().contentEquals("t")) { + workingCopy.rewrite(node.getType(), make.Identifier("Test2")); + } + return super.visitVariable(node, p); + } + @Override + public Void visitIdentifier(IdentifierTree node, Void p) { + if (node.getName().contentEquals("o")) { + workingCopy.rewrite(node, workingCopy.getTreeMaker().setLabel(node, "o2")); + } + return super.visitIdentifier(node, p); + } + }.scan(cut, null); + } + + }; + src.runModificationTask(task).commit(); + String res = TestUtilities.copyFileToString(testFile); + System.err.println(res); + assertEquals(golden, res); + } + private boolean typeTestPatternAvailable() { try { - Class.forName("com.sun.source.tree.BindingPatternTree", false, JCTree.class.getClassLoader()); + Class.forName("com.sun.source.tree.BindingPatternTree", false, Tree.class.getClassLoader()).getDeclaredMethod("getVariable"); return true; - } catch (ClassNotFoundException ex) { + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) { //OK return false; } diff --git a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java index 0f2686fb4e3d..f3d878d3e831 100644 --- a/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java +++ b/java/refactoring.java/test/unit/src/org/netbeans/modules/refactoring/java/test/RenameTest.java @@ -1483,6 +1483,27 @@ public void testRenameRecordPropa() throws Exception { } + public void testRenameBindingVariableType() throws Exception { + if (!typeTestPatternAvailable()) return; //only run the test when javac supports it + writeFilesAndWaitForScan(src, + new File("t/A.java", "package t;\n" + + "public class A {\n" + + " public boolean taragui(Object o) {\n" + + " return o instanceof A a && a.toString() != null;\n" + + " }\n" + + "}")); + JavaRenameProperties props = new JavaRenameProperties(); + performRename(src.getFileObject("t/A.java"), 25, "B", props, true); + verifyContent(src, + new File("t/A.java", "package t;\n" + + "public class B {\n" + + " public boolean taragui(Object o) {\n" + + " return o instanceof B a && a.toString() != null;\n" + + " }\n" + + "}")); + + } + private void performRename(FileObject source, final int position, final int position2, final String newname, final JavaRenameProperties props, final boolean searchInComments, Problem... expectedProblems) throws Exception { final RenameRefactoring[] r = new RenameRefactoring[1]; JavaSource.forFileObject(source).runUserActionTask(new Task() { @@ -1557,4 +1578,14 @@ public void run(CompilationController javac) throws Exception { assertProblems(Arrays.asList(expectedProblems), problems); } + + private boolean typeTestPatternAvailable() { + try { + Class.forName("com.sun.source.tree.BindingPatternTree", false, Tree.class.getClassLoader()).getDeclaredMethod("getVariable"); + return true; + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) { + //OK + return false; + } + } }