From 529693c916ebca6226ae501c1ceaf53eb88f95ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fournier?= Date: Wed, 13 Nov 2024 22:27:51 +0100 Subject: [PATCH 1/3] [java] fix inference dependency issue Reported in #5324 I improved the verbose logging output a bit so some of the changes are not directly relevant. --- .../types/internal/infer/ExprCheckHelper.java | 18 +- .../lang/java/types/internal/infer/Infer.java | 13 +- .../internal/infer/InferenceContext.java | 87 +++++++- .../internal/infer/TypeInferenceLogger.java | 74 +++++-- .../types/internal/infer/VarWalkStrategy.java | 8 + .../lang/java/types/TypesTreeDumpTest.java | 5 + .../bestpractices/xml/UnusedPrivateMethod.xml | 43 ++++ .../NestedLambdasAndMethodCalls.java | 38 ++++ .../dumptests/NestedLambdasAndMethodCalls.txt | 194 ++++++++++++++++++ .../java/types/dumptests/UnnamedPatterns.txt | 6 +- 10 files changed, 448 insertions(+), 38 deletions(-) create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.java create mode 100644 pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.txt diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java index c5c20caec9..dc8955fcb0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java @@ -552,8 +552,15 @@ final class ExprCheckHelper { // finally, add bounds if (result != ts.NO_TYPE) { + Set inputIvars = infCtx.freeVarsIn(groundFun.getFormalParameters()); + // The free vars of the return type depend on the free vars of the parameters. + // This explicit dependency is there to prevent solving the variables in the + // return type before solving those of the parameters. That is because the variables + // mentioned in the return type may be further constrained by adding the return constraints + // below (in the listener), which is only triggered when the input ivars have been instantiated. + infCtx.addInstantiationDependencies(infCtx.freeVarsIn(groundFun.getReturnType()), inputIvars); infCtx.addInstantiationListener( - infCtx.freeVarsIn(groundFun.getFormalParameters()), + inputIvars, solvedCtx -> { if (mayMutateExpr()) { lambda.setInferredType(solvedCtx.ground(groundTargetType)); @@ -562,8 +569,15 @@ final class ExprCheckHelper { lambda.updateTypingContext(solvedGroundFun); } JTypeMirror groundResult = solvedCtx.ground(result); + // We need to build another checker that uses the solved context. + // This is because the free vars may have been adopted by a parent + // context, so the solvedCtx may be that parent context. The checks + // must use that context so that constraints and listeners are added + // to the parent context, since that one is responsible for solving + // the variables. + ExprCheckHelper newChecker = new ExprCheckHelper(solvedCtx, phase, this.checker, site, infer); for (ExprMirror expr : lambda.getResultExpressions()) { - if (!isCompatible(groundResult, expr)) { + if (!newChecker.isCompatible(groundResult, expr)) { return; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java index 02f16611d5..7982304210 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/Infer.java @@ -602,17 +602,20 @@ public final class Infer { // see: https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html#jls-18.5.1 // as per https://docs.oracle.com/javase/specs/jls/se9/html/jls-18.html#jls-18.5.2 // we only test it can reduce, we don't commit inferred types at this stage - InferenceContext ctxCopy = infCtx.copy(); - LOG.applicabilityTest(ctxCopy, m); - ctxCopy.solve(/*onlyBoundedVars:*/isPreJava8()); - + InferenceContext ctxCopy = infCtx.shallowCopy(); + LOG.applicabilityTest(ctxCopy); + try { + ctxCopy.solve(/*onlyBoundedVars:*/isPreJava8()); + } finally { + LOG.finishApplicabilityTest(); + } // if unchecked conversion was needed, update the site for invocation pass if (ctxCopy.needsUncheckedConversion()) { site.setNeedsUncheckedConversion(); } // don't commit any types - return m; + return infCtx.mapToIVars(m); } } finally { // Note that even if solve succeeded, listeners checking deferred diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java index ba3a78640e..73b843439f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java @@ -13,11 +13,13 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Supplier; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -38,6 +40,7 @@ import net.sourceforge.pmd.lang.java.types.internal.infer.IncorporationAction.Pr import net.sourceforge.pmd.lang.java.types.internal.infer.IncorporationAction.SubstituteInst; import net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind; import net.sourceforge.pmd.lang.java.types.internal.infer.VarWalkStrategy.GraphWalk; +import net.sourceforge.pmd.util.CollectionUtil; /** * Context of a type inference process. This object maintains a set of @@ -51,6 +54,13 @@ final class InferenceContext { private static int ctxId = 0; private final Map> instantiationListeners = new HashMap<>(); + // explicit dependencies between variables for graph building + private final Map> instantiationConstraints = new HashMap<>(); + // This flag is set to true when the explicit dependencies are changed, + // or when this context adopted new ivars. This means we should interrupt + // resolution and recompute the dependency graph between ivars, because + // the new variables may have dependencies on existing variables, and vice versa. + private boolean graphWasChanged = false; private final Set freeVars = new LinkedHashSet<>(); private final Set inferenceVars = new LinkedHashSet<>(); @@ -127,18 +137,19 @@ final class InferenceContext { } } - public InferenceContext copy() { + /** + * Performs a shallow copy of this context, which would allow solving + * the variables without executing listeners. Instantiation listeners + * are not copied, and parent contexts are not copied. + */ + public InferenceContext shallowCopy() { final InferenceContext copy = new InferenceContext(ts, supertypeCheckCache, Collections.emptyList(), logger); copy.freeVars.addAll(this.freeVars); copy.inferenceVars.addAll(this.inferenceVars); copy.incorporationActions.addAll(this.incorporationActions); + copy.instantiationConstraints.putAll(this.instantiationConstraints); copy.mapping = mapping; // mapping is immutable, so we can share it safely - // recursively copy parents… - if (this.parent != null) { - copy.parent = this.parent.copy(); - } - return copy; } @@ -310,10 +321,20 @@ final class InferenceContext { * Copy variable in this inference context to the given context */ void duplicateInto(final InferenceContext that) { + boolean changedGraph = !that.freeVars.containsAll(this.freeVars) + || !this.instantiationConstraints.isEmpty(); + that.graphWasChanged |= changedGraph; that.inferenceVars.addAll(this.inferenceVars); that.freeVars.addAll(this.freeVars); that.incorporationActions.addAll(this.incorporationActions); that.instantiationListeners.putAll(this.instantiationListeners); + CollectionUtil.mergeMaps( + that.instantiationConstraints, + this.instantiationConstraints, + (set1, set2) -> { + set1.addAll(set2); + return set1; + }); this.parent = that; @@ -324,6 +345,30 @@ final class InferenceContext { } + // The `from` ivars depend on the `dependencies` ivars for resolution. + void addInstantiationDependencies(Set from, Set dependencies) { + if (from.isEmpty()) { + return; + } + Set outputVars = new HashSet<>(dependencies); + outputVars.removeAll(from); + if (outputVars.isEmpty()) { + return; + } + for (InferenceVar inputVar : from) { + logger.ivarDependencyRegistered(this, inputVar, outputVars); + instantiationConstraints.merge(inputVar, outputVars, (o1, o2) -> { + o2 = new LinkedHashSet<>(o2); + o2.addAll(o1); + return o2; + }); + } + } + + Map> getInstantiationDependencies() { + return instantiationConstraints; + } + void addInstantiationListener(Set relevantTypes, InstantiationListener listener) { Set free = freeVarsIn(relevantTypes); if (free.isEmpty()) { @@ -448,7 +493,7 @@ final class InferenceContext { } boolean solve(boolean onlyBoundedVars) { - return solve(new GraphWalk(this, onlyBoundedVars)); + return solve(() -> new GraphWalk(this, onlyBoundedVars)); } /** @@ -459,6 +504,26 @@ final class InferenceContext { solve(new GraphWalk(var)); } + + private boolean solve(Supplier newWalker) { + VarWalkStrategy strategy = newWalker.get(); + while (strategy != null) { + if (solve(strategy)) { + break; + } + strategy = newWalker.get(); + } + return freeVars.isEmpty(); + } + + + /** + * This returns true if solving the VarWalkStrategy succeeded entirely. + * Resolution can be interrupted early to account for new ivars and dependencies, + * which may change the graph dependencies. In this case this method returns + * false, we recompute the graph with the new ivars and dependencies, and + * we try again to make progress. + */ private boolean solve(VarWalkStrategy walker) { incorporate(); @@ -470,6 +535,12 @@ final class InferenceContext { //repeat until all variables are solved outer: while (!intersect(freeVars, varsToSolve).isEmpty() && progress) { + if (graphWasChanged) { + graphWasChanged = false; + logger.contextDependenciesChanged(this); + return false; + } + progress = false; for (List wave : ReductionStep.WAVES) { if (solveBatchProgressed(varsToSolve, wave)) { @@ -481,7 +552,7 @@ final class InferenceContext { } } } - return freeVars.isEmpty(); + return true; } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java index 023e422a49..2c9cfff9ef 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java @@ -12,6 +12,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -61,7 +62,10 @@ public interface TypeInferenceLogger { default void ctxInitialization(InferenceContext ctx, JMethodSig sig) { } - default void applicabilityTest(InferenceContext ctx, JMethodSig sig) { } + default void applicabilityTest(InferenceContext ctx) { } + + default void finishApplicabilityTest() { + } default void startArgsChecks() { } @@ -81,6 +85,8 @@ public interface TypeInferenceLogger { default void propagateAndAbort(InferenceContext context, InferenceContext parent) { } + default void contextDependenciesChanged(InferenceContext ctx) { } + // ivar events @@ -90,6 +96,8 @@ public interface TypeInferenceLogger { default void ivarInstantiated(InferenceContext ctx, InferenceVar var, JTypeMirror inst) { } + default void ivarDependencyRegistered(InferenceContext ctx, InferenceVar var, Set deps) { } + /** * Log that the instantiation of the method type m for the given @@ -136,9 +144,11 @@ public interface TypeInferenceLogger { protected final PrintStream out; - protected static final int LEVEL_INCREMENT = 4; - private int level; private String indent; + /** + * Four spaces. + */ + protected static final String BASE_INDENT = " "; protected static final String ANSI_RESET = "\u001B[0m"; protected static final String ANSI_BLUE = "\u001B[34m"; @@ -177,16 +187,24 @@ public interface TypeInferenceLogger { public SimpleLogger(PrintStream out) { this.out = out; - updateLevel(0); + this.indent = ""; } - protected int getLevel() { - return level; + protected void addIndentSegment(String segment) { + indent += segment; } - protected void updateLevel(int increment) { - level += increment; - indent = StringUtils.repeat(' ', level); + protected void removeIndentSegment(String segment) { + assert indent.endsWith(segment) : "mismatched end section!"; + indent = StringUtils.removeEnd(indent, segment); + } + + protected void setIndent(String indent) { + this.indent = indent; + } + + protected String getIndent() { + return indent; } protected void println(String str) { @@ -196,13 +214,13 @@ public interface TypeInferenceLogger { protected void endSection(String footer) { - updateLevel(-LEVEL_INCREMENT); + removeIndentSegment(BASE_INDENT); println(footer); } protected void startSection(String header) { println(header); - updateLevel(+LEVEL_INCREMENT); + addIndentSegment(BASE_INDENT); } @Override @@ -335,7 +353,7 @@ public interface TypeInferenceLogger { class VerboseLogger extends SimpleLogger { - private final Deque marks = new ArrayDeque<>(); + private final Deque marks = new ArrayDeque<>(); public VerboseLogger(PrintStream out) { super(out); @@ -343,16 +361,16 @@ public interface TypeInferenceLogger { } void mark() { - marks.push(getLevel()); + marks.push(getIndent()); } void rollback(String lastWords) { - int pop = marks.pop(); - updateLevel(pop - getLevel()); // back to normal + final String savedIndent = marks.pop(); + setIndent(savedIndent); // back to normal if (!lastWords.isEmpty()) { - updateLevel(+LEVEL_INCREMENT); + addIndentSegment(BASE_INDENT); println(lastWords); - updateLevel(-LEVEL_INCREMENT); + setIndent(savedIndent); } } @@ -369,8 +387,14 @@ public interface TypeInferenceLogger { } @Override - public void applicabilityTest(InferenceContext ctx, JMethodSig sig) { - println(String.format("Applicability testing with Context %-11d%s", ctx.getId(), ppHighlight(ctx.mapToIVars(sig)))); + public void applicabilityTest(InferenceContext ctx) { + println(String.format("Solving with context %d for applicability testing", ctx.getId())); + addIndentSegment("| "); + } + + @Override + public void finishApplicabilityTest() { + removeIndentSegment("| "); } @Override @@ -404,7 +428,7 @@ public interface TypeInferenceLogger { @Override public void startArg(int i, ExprMirror expr, JTypeMirror formalType) { - startSection("Checking arg " + i + " against " + formalType); + startSection("Checking arg " + i + " against " + colorIvars(formalType)); printExpr(expr); } @@ -452,6 +476,16 @@ public interface TypeInferenceLogger { println(addCtxInfo(ctx, "Ivar instantiated") + color(var + " := ", ANSI_BLUE) + colorIvars(inst)); } + @Override + public void ivarDependencyRegistered(InferenceContext ctx, InferenceVar var, Set deps) { + println(addCtxInfo(ctx, "Ivar dependency registered: ") + color(var + " -> ", ANSI_BLUE) + colorIvars(deps)); + } + + @Override + public void contextDependenciesChanged(InferenceContext ctx) { + println("Recomputing dependency graph (ctx " + ctx.getId() + ")"); + } + private @NonNull String addCtxInfo(InferenceContext ctx, String event) { return String.format("%-20s(ctx %d): ", event, ctx.getId()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/VarWalkStrategy.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/VarWalkStrategy.java index 2177a1cf92..19774ca49e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/VarWalkStrategy.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/VarWalkStrategy.java @@ -90,6 +90,14 @@ interface VarWalkStrategy extends Iterator> { } } + ctx.getInstantiationDependencies().forEach((ivar, deps) -> { + Vertex vertex = graph.addLeaf(ivar); + for (InferenceVar dep : deps) { + Vertex target = graph.addLeaf(dep); + graph.addEdge(vertex, target); + } + }); + // Here, "α depends on β" is modelled by an edge α -> β // Merge strongly connected components into a "super node". diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java index f69b79479c..9b8baaeca6 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java @@ -50,6 +50,11 @@ class TypesTreeDumpTest extends BaseTreeDumpTest { doTest("UnnamedPatterns"); } + @Test + void testNestedLambdasAndMethodCalls() { + doTest("NestedLambdasAndMethodCalls"); + } + @Override protected @NonNull String normalize(@NonNull String str) { return super.normalize(str) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index e81d963fcb..2679f0a8c5 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -2134,4 +2134,47 @@ public class ObtainViaTest { } ]]> + + UnusedPrivateMethod with method reference + 0 + > map = new Main().run(library); + System.out.println(map); + } + + private Map> run(Library library) { + return library + .books() + .stream() + .map(book -> book.lenders().stream().collect(Collectors.toMap(Lender::name, lender -> Map.of(book.title(), lender.status())))) + .reduce(this::reduceBooksAndLenderStatusByLender) + .orElse(null); + } + + private Map> reduceBooksAndLenderStatusByLender( + Map> previousMap, + Map> nextMap + ) { + previousMap.putAll(nextMap); + return previousMap; + } + } + + + record Lender(String name, String status) {} + record Book(String title, Collection lenders) {} + record Library(Collection books) {} + ]]> + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.java new file mode 100644 index 0000000000..33914e0a68 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.java @@ -0,0 +1,38 @@ +package org.example.unusedPrivateMethod; + +import static java.util.Collections.emptySet; + +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; + +public class NestedLambdasAndMethodCalls { + + public static void main(String[] args) { + Library library = new Library(emptySet()); + Map> map = new Main().run(library); + System.out.println(map); + } + + private Map> run(Library library) { + return library + .books() + .stream() + .map(book -> book.lenders().stream().collect(Collectors.toMap(Lender::name, lender -> Map.of(book.title(), lender.status())))) + .reduce(this::reduceBooksAndLenderStatusByLender) + .orElse(null); + } + + private Map> reduceBooksAndLenderStatusByLender( + Map> previousMap, + Map> nextMap + ) { + previousMap.putAll(nextMap); + return previousMap; + } +} + + +record Lender(String name, String status) {} +record Book(String title, Collection lenders) {} +record Library(Collection books) {} \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.txt new file mode 100644 index 0000000000..9099296ff8 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/NestedLambdasAndMethodCalls.txt @@ -0,0 +1,194 @@ ++- CompilationUnit[] + +- PackageDeclaration[] + | +- ModifierList[] + +- ImportDeclaration[] + +- ImportDeclaration[] + +- ImportDeclaration[] + +- ImportDeclaration[] + +- ClassDeclaration[@TypeMirror = "org.example.unusedPrivateMethod.NestedLambdasAndMethodCalls"] + | +- ModifierList[] + | +- ClassBody[] + | +- MethodDeclaration[@Name = "main"] + | | +- ModifierList[] + | | +- VoidType[@TypeMirror = "void"] + | | +- FormalParameters[] + | | | +- FormalParameter[@TypeMirror = "java.lang.String[]"] + | | | +- ModifierList[] + | | | +- ArrayType[@TypeMirror = "java.lang.String[]"] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | | +- ArrayDimensions[] + | | | | +- ArrayTypeDim[] + | | | +- VariableId[@Name = "args", @TypeMirror = "java.lang.String[]"] + | | +- Block[] + | | +- LocalVariableDeclaration[] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | +- VariableDeclarator[] + | | | +- VariableId[@Name = "library", @TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | +- ConstructorCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Library.new(java.util.Collection) -> org.example.unusedPrivateMethod.Library", @MethodName = "new", @TypeMirror = "org.example.unusedPrivateMethod.Library", @Unchecked = false, @VarargsCall = false] + | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | +- ArgumentList[] + | | | +- MethodCall[@Failed = false, @Function = "java.util.Collections. emptySet() -> java.util.Set", @MethodName = "emptySet", @TypeMirror = "java.util.Set", @Unchecked = false, @VarargsCall = false] + | | | +- ArgumentList[] + | | +- LocalVariableDeclaration[] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "java.util.Map>"] + | | | | +- TypeArguments[] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | | +- ClassType[@TypeMirror = "java.util.Map"] + | | | | +- TypeArguments[] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- VariableDeclarator[] + | | | +- VariableId[@Name = "map", @TypeMirror = "java.util.Map>"] + | | | +- MethodCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "run", @TypeMirror = "(*unknown*)", @Unchecked = false, @VarargsCall = false] + | | | +- ConstructorCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "new", @TypeMirror = "*Main", @Unchecked = false, @VarargsCall = false] + | | | | +- ClassType[@TypeMirror = "*Main"] + | | | | +- ArgumentList[] + | | | +- ArgumentList[] + | | | +- VariableAccess[@Name = "library", @TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | +- ExpressionStatement[] + | | +- MethodCall[@Failed = false, @Function = "java.io.PrintStream.println(java.lang.Object) -> void", @MethodName = "println", @TypeMirror = "void", @Unchecked = false, @VarargsCall = false] + | | +- FieldAccess[@Name = "out", @TypeMirror = "java.io.PrintStream"] + | | | +- TypeExpression[@TypeMirror = "java.lang.System"] + | | | +- ClassType[@TypeMirror = "java.lang.System"] + | | +- ArgumentList[] + | | +- VariableAccess[@Name = "map", @TypeMirror = "java.util.Map>"] + | +- MethodDeclaration[@Name = "run"] + | | +- ModifierList[] + | | +- ClassType[@TypeMirror = "java.util.Map>"] + | | | +- TypeArguments[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- ClassType[@TypeMirror = "java.util.Map"] + | | | +- TypeArguments[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | +- FormalParameters[] + | | | +- FormalParameter[@TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | +- VariableId[@Name = "library", @TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | +- Block[] + | | +- ReturnStatement[] + | | +- MethodCall[@Failed = false, @Function = "java.util.Optional>>.orElse(java.util.Map>) -> java.util.Map>", @MethodName = "orElse", @TypeMirror = "java.util.Map>", @Unchecked = false, @VarargsCall = false] + | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream>>.reduce(java.util.function.BinaryOperator>>) -> java.util.Optional>>", @MethodName = "reduce", @TypeMirror = "java.util.Optional>>", @Unchecked = false, @VarargsCall = false] + | | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream. map(java.util.function.Function>>) -> java.util.stream.Stream>>", @MethodName = "map", @TypeMirror = "java.util.stream.Stream>>", @Unchecked = false, @VarargsCall = false] + | | | | +- MethodCall[@Failed = false, @Function = "java.util.Collection.stream() -> java.util.stream.Stream", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream", @Unchecked = false, @VarargsCall = false] + | | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Library.books() -> java.util.Collection", @MethodName = "books", @TypeMirror = "java.util.Collection", @Unchecked = false, @VarargsCall = false] + | | | | | | +- VariableAccess[@Name = "library", @TypeMirror = "org.example.unusedPrivateMethod.Library"] + | | | | | | +- ArgumentList[] + | | | | | +- ArgumentList[] + | | | | +- ArgumentList[] + | | | | +- LambdaExpression[@TypeMirror = "java.util.function.Function>>"] + | | | | +- LambdaParameterList[] + | | | | | +- LambdaParameter[@TypeMirror = "org.example.unusedPrivateMethod.Book"] + | | | | | +- ModifierList[] + | | | | | +- VariableId[@Name = "book", @TypeMirror = "org.example.unusedPrivateMethod.Book"] + | | | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream. collect(java.util.stream.Collector>>) -> java.util.Map>", @MethodName = "collect", @TypeMirror = "java.util.Map>", @Unchecked = false, @VarargsCall = false] + | | | | +- MethodCall[@Failed = false, @Function = "java.util.Collection.stream() -> java.util.stream.Stream", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream", @Unchecked = false, @VarargsCall = false] + | | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Book.lenders() -> java.util.Collection", @MethodName = "lenders", @TypeMirror = "java.util.Collection", @Unchecked = false, @VarargsCall = false] + | | | | | | +- VariableAccess[@Name = "book", @TypeMirror = "org.example.unusedPrivateMethod.Book"] + | | | | | | +- ArgumentList[] + | | | | | +- ArgumentList[] + | | | | +- ArgumentList[] + | | | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors. toMap(java.util.function.Function, java.util.function.Function>) -> java.util.stream.Collector>>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector>>", @Unchecked = false, @VarargsCall = false] + | | | | +- TypeExpression[@TypeMirror = "java.util.stream.Collectors"] + | | | | | +- ClassType[@TypeMirror = "java.util.stream.Collectors"] + | | | | +- ArgumentList[] + | | | | +- MethodReference[@TypeMirror = "java.util.function.Function"] + | | | | | +- TypeExpression[@TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | | | +- LambdaExpression[@TypeMirror = "java.util.function.Function>"] + | | | | +- LambdaParameterList[] + | | | | | +- LambdaParameter[@TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | | | | +- ModifierList[] + | | | | | +- VariableId[@Name = "lender", @TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | | | +- MethodCall[@Failed = false, @Function = "java.util.Map. of(java.lang.String, java.lang.String) -> java.util.Map", @MethodName = "of", @TypeMirror = "java.util.Map", @Unchecked = false, @VarargsCall = false] + | | | | +- TypeExpression[@TypeMirror = "java.util.Map"] + | | | | | +- ClassType[@TypeMirror = "java.util.Map"] + | | | | +- ArgumentList[] + | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Book.title() -> java.lang.String", @MethodName = "title", @TypeMirror = "java.lang.String", @Unchecked = false, @VarargsCall = false] + | | | | | +- VariableAccess[@Name = "book", @TypeMirror = "org.example.unusedPrivateMethod.Book"] + | | | | | +- ArgumentList[] + | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Lender.status() -> java.lang.String", @MethodName = "status", @TypeMirror = "java.lang.String", @Unchecked = false, @VarargsCall = false] + | | | | +- VariableAccess[@Name = "lender", @TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | | | +- ArgumentList[] + | | | +- ArgumentList[] + | | | +- MethodReference[@TypeMirror = "java.util.function.BinaryOperator>>"] + | | | +- ThisExpression[@TypeMirror = "org.example.unusedPrivateMethod.NestedLambdasAndMethodCalls"] + | | +- ArgumentList[] + | | +- NullLiteral[@TypeMirror = "null"] + | +- MethodDeclaration[@Name = "reduceBooksAndLenderStatusByLender"] + | +- ModifierList[] + | +- ClassType[@TypeMirror = "java.util.Map>"] + | | +- TypeArguments[] + | | +- ClassType[@TypeMirror = "java.lang.String"] + | | +- ClassType[@TypeMirror = "java.util.Map"] + | | +- TypeArguments[] + | | +- ClassType[@TypeMirror = "java.lang.String"] + | | +- ClassType[@TypeMirror = "java.lang.String"] + | +- FormalParameters[] + | | +- FormalParameter[@TypeMirror = "java.util.Map>"] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "java.util.Map>"] + | | | | +- TypeArguments[] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | | +- ClassType[@TypeMirror = "java.util.Map"] + | | | | +- TypeArguments[] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- VariableId[@Name = "previousMap", @TypeMirror = "java.util.Map>"] + | | +- FormalParameter[@TypeMirror = "java.util.Map>"] + | | +- ModifierList[] + | | +- ClassType[@TypeMirror = "java.util.Map>"] + | | | +- TypeArguments[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- ClassType[@TypeMirror = "java.util.Map"] + | | | +- TypeArguments[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | +- VariableId[@Name = "nextMap", @TypeMirror = "java.util.Map>"] + | +- Block[] + | +- ExpressionStatement[] + | | +- MethodCall[@Failed = false, @Function = "java.util.Map>.putAll(java.util.Map>) -> void", @MethodName = "putAll", @TypeMirror = "void", @Unchecked = false, @VarargsCall = false] + | | +- VariableAccess[@Name = "previousMap", @TypeMirror = "java.util.Map>"] + | | +- ArgumentList[] + | | +- VariableAccess[@Name = "nextMap", @TypeMirror = "java.util.Map>"] + | +- ReturnStatement[] + | +- VariableAccess[@Name = "previousMap", @TypeMirror = "java.util.Map>"] + +- RecordDeclaration[@TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | +- ModifierList[] + | +- RecordComponentList[] + | | +- RecordComponent[@TypeMirror = "java.lang.String"] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- VariableId[@Name = "name", @TypeMirror = "java.lang.String"] + | | +- RecordComponent[@TypeMirror = "java.lang.String"] + | | +- ModifierList[] + | | +- ClassType[@TypeMirror = "java.lang.String"] + | | +- VariableId[@Name = "status", @TypeMirror = "java.lang.String"] + | +- RecordBody[] + +- RecordDeclaration[@TypeMirror = "org.example.unusedPrivateMethod.Book"] + | +- ModifierList[] + | +- RecordComponentList[] + | | +- RecordComponent[@TypeMirror = "java.lang.String"] + | | | +- ModifierList[] + | | | +- ClassType[@TypeMirror = "java.lang.String"] + | | | +- VariableId[@Name = "title", @TypeMirror = "java.lang.String"] + | | +- RecordComponent[@TypeMirror = "java.util.Collection"] + | | +- ModifierList[] + | | +- ClassType[@TypeMirror = "java.util.Collection"] + | | | +- TypeArguments[] + | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Lender"] + | | +- VariableId[@Name = "lenders", @TypeMirror = "java.util.Collection"] + | +- RecordBody[] + +- RecordDeclaration[@TypeMirror = "org.example.unusedPrivateMethod.Library"] + +- ModifierList[] + +- RecordComponentList[] + | +- RecordComponent[@TypeMirror = "java.util.Collection"] + | +- ModifierList[] + | +- ClassType[@TypeMirror = "java.util.Collection"] + | | +- TypeArguments[] + | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Book"] + | +- VariableId[@Name = "books", @TypeMirror = "java.util.Collection"] + +- RecordBody[] diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnnamedPatterns.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnnamedPatterns.txt index 25eb7dd0d7..e1fa78ded1 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnnamedPatterns.txt +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnnamedPatterns.txt @@ -583,7 +583,7 @@ | +- ArgumentList[] | +- StringLiteral[@TypeMirror = "java.lang.String"] +- ExpressionStatement[] - +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream. collect(java.util.stream.Collector>) -> java.util.Map", @MethodName = "collect", @TypeMirror = "java.util.Map", @Unchecked = false, @VarargsCall = false] + +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream. collect(java.util.stream.Collector>) -> java.util.Map", @MethodName = "collect", @TypeMirror = "java.util.Map", @Unchecked = false, @VarargsCall = false] +- MethodCall[@Failed = false, @Function = "java.util.Collection.stream() -> java.util.stream.Stream", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream", @Unchecked = false, @VarargsCall = false] | +- MethodCall[@Failed = false, @Function = "java.util.List. of(java.lang.String, java.lang.String) -> java.util.List", @MethodName = "of", @TypeMirror = "java.util.List", @Unchecked = false, @VarargsCall = false] | | +- TypeExpression[@TypeMirror = "java.util.List"] @@ -593,14 +593,14 @@ | | +- StringLiteral[@TypeMirror = "java.lang.String"] | +- ArgumentList[] +- ArgumentList[] - +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors. toMap(java.util.function.Function, java.util.function.Function) -> java.util.stream.Collector>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector>", @Unchecked = false, @VarargsCall = false] + +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors. toMap(java.util.function.Function, java.util.function.Function) -> java.util.stream.Collector>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector>", @Unchecked = false, @VarargsCall = false] +- TypeExpression[@TypeMirror = "java.util.stream.Collectors"] | +- ClassType[@TypeMirror = "java.util.stream.Collectors"] +- ArgumentList[] +- MethodReference[@TypeMirror = "java.util.function.Function"] | +- TypeExpression[@TypeMirror = "java.lang.String"] | +- ClassType[@TypeMirror = "java.lang.String"] - +- LambdaExpression[@TypeMirror = "java.util.function.Function"] + +- LambdaExpression[@TypeMirror = "java.util.function.Function"] +- LambdaParameterList[] | +- LambdaParameter[@TypeMirror = "java.lang.String"] | +- ModifierList[] From e5a123698144993b6cf8808e6206fae3b4ff5d88 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 14 Nov 2024 15:48:47 +0100 Subject: [PATCH 2/3] Update pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml --- .../lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index 2679f0a8c5..2a743036a6 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -2135,7 +2135,7 @@ public class ObtainViaTest { ]]> - UnusedPrivateMethod with method reference + #5324 UnusedPrivateMethod with method reference 0 Date: Thu, 14 Nov 2024 15:50:35 +0100 Subject: [PATCH 3/3] [doc] Update release notes (#5324) --- docs/pages/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 3e50f0fc86..5fa3ac04b6 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,6 +19,7 @@ This is a {{ site.pmd.release_type }} release. * [#1860](https://github.com/pmd/pmd/issues/1860): \[ant] Reflective access warnings on java > 9 and java < 17 * java * [#5293](https://github.com/pmd/pmd/issues/5293): \[java] Deadlock when executing PMD in multiple threads + * [#5324](https://github.com/pmd/pmd/issues/5324): \[java] Issue with type inference of nested lambdas ### 🚨 API Changes