Merge branch 'main' into issue-5287

This commit is contained in:
Juan Martín Sotuyo Dodero 2024-11-15 08:51:52 -06:00 committed by GitHub
commit 4e1f4be009
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1046 additions and 165 deletions

View File

@ -7865,6 +7865,24 @@
"contributions": [
"code"
]
},
{
"login": "chenguangqi",
"name": "天热吃西瓜",
"avatar_url": "https://avatars.githubusercontent.com/u/6231010?v=4",
"profile": "http://chenguangqi.github.io/",
"contributions": [
"bug"
]
},
{
"login": "wahajenius",
"name": "Willem A. Hajenius",
"avatar_url": "https://avatars.githubusercontent.com/u/7836322?v=4",
"profile": "https://github.com/wahajenius",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -75,7 +75,7 @@ GEM
racc (1.8.1)
rchardet (1.8.0)
rexml (3.3.9)
rouge (4.4.0)
rouge (4.5.0)
rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1)
safe_yaml (1.0.5)

File diff suppressed because it is too large Load Diff

View File

@ -17,10 +17,17 @@ This is a {{ site.pmd.release_type }} release.
### 🐛 Fixed Issues
* ant
* [#1860](https://github.com/pmd/pmd/issues/1860): \[ant] Reflective access warnings on java > 9 and java < 17
* apex
* [#5333](https://github.com/pmd/pmd/issues/5333): \[apex] Token recognition errors for string containing unicode escape sequence
* html
* [5322](https://github.com/pmd/pmd/issues/5322): \[html] CPD throws exception on when HTML file is missing closing tag
* 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
* [#5329](https://github.com/pmd/pmd/issues/5329): \[java] Type inference issue with unknown method ref in call chain
* java-performance
* [#5287](https://github.com/pmd/pmd/issues/5287): \[java] TooFewBranchesForSwitch false-positive with switch using list of case constants
* [#5314](https://github.com/pmd/pmd/issues/5314): \[java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters
### 🚨 API Changes
@ -30,6 +37,7 @@ This is a {{ site.pmd.release_type }} release.
instead (note different package `ast` instead of `antlr4`).
### ✨ External Contributions
* [#5284](https://github.com/pmd/pmd/pull/5284): \[apex] Use case-insensitive input stream to avoid choking on Unicode escape sequences - [Willem A. Hajenius](https://github.com/wahajenius) (@wahajenius)
{% endtocmaker %}

View File

@ -14,14 +14,19 @@ import java.util.List;
import java.util.Map;
import java.util.RandomAccess;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.LexException;
import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.document.TextRegion;
import io.github.apexdevtools.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.CaseInsensitiveInputStream;
@InternalApi
final class ApexCommentBuilder {
@ -103,7 +108,15 @@ final class ApexCommentBuilder {
}
private static CommentInformation extractInformationFromComments(TextDocument sourceCode, String suppressMarker) {
ApexLexer lexer = new ApexLexer(CharStreams.fromString(sourceCode.getText().toString()));
String source = sourceCode.getText().toString();
ApexLexer lexer = new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(source)));
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
throw new LexException(line, charPositionInLine, sourceCode.getFileId(), msg, e);
}
});
List<Token> allCommentTokens = new ArrayList<>();
Map<Integer, String> suppressMap = new HashMap<>();

View File

@ -66,4 +66,12 @@ class ApexCommentTest extends ApexParserTestBase {
ASTFormalComment comment = file.descendants(ASTUserClass.class).children(ASTFormalComment.class).first();
assertEquals(FORMAL_COMMENT_CONTENT, comment.getImage());
}
@Test
void fileWithUnicodeEscapes() {
ASTApexFile file = apex.parse(FORMAL_COMMENT_CONTENT + "\n"
+ "class MyClass { String s = 'Fran\\u00E7ois'; }");
ASTFormalComment comment = file.descendants(ASTUserClass.class).children(ASTFormalComment.class).first();
assertEquals(FORMAL_COMMENT_CONTENT, comment.getImage());
}
}

View File

@ -8,14 +8,18 @@ package net.sourceforge.pmd.lang.apex.ast;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.junit.jupiter.api.Test;
import io.github.apexdevtools.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.ApexParser;
import io.github.apexdevtools.apexparser.CaseInsensitiveInputStream;
/**
* This is an exploration test for {@link ApexLexer}.
@ -49,4 +53,36 @@ class ApexLexerTest {
ApexParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
assertNotNull(compilationUnit);
}
@Test
void testLexerUnicodeEscapes() {
String s = "'Fran\\u00E7ois'";
// note: with apex-parser 4.3.1, no errors are reported anymore
assertEquals(2, getLexingErrors(CharStreams.fromString(s)));
assertEquals(0, getLexingErrors(new CaseInsensitiveInputStream(CharStreams.fromString(s))));
}
private int getLexingErrors(CharStream stream) {
ApexLexer lexer = new ApexLexer(stream);
ErrorListener errorListener = new ErrorListener();
lexer.removeErrorListeners(); // Avoid distracting "token recognition error" stderr output
lexer.addErrorListener(errorListener);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
return errorListener.getErrorCount();
}
private static class ErrorListener extends BaseErrorListener {
private int errorCount = 0;
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
++errorCount;
}
public int getErrorCount() {
return errorCount;
}
}
}

View File

@ -178,7 +178,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.0</version>
<version>1.27.1</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -50,15 +50,14 @@ class LineNumbers {
nextIndex = determineLocation((AbstractHtmlNode<?>) child, nextIndex);
}
// autoclosing element, eg <a />
boolean isAutoClose = n.getNumChildren() == 0
&& n instanceof ASTHtmlElement
// nextIndex is up to the closing > at this point
&& htmlString.startsWith("/>", nextIndex - 2);
// explicitly closing element, eg. </a>
boolean hasCloseElement = n instanceof ASTHtmlElement
// nextIndex is up to the closing tag at this point
&& htmlString.startsWith("</" + n.getXPathNodeName() + ">", nextIndex);
if (n instanceof ASTHtmlDocument) {
nextIndex = htmlString.length();
} else if (n instanceof ASTHtmlElement && !isAutoClose) {
} else if (n instanceof ASTHtmlElement && hasCloseElement) {
nextIndex += 2 + n.getXPathNodeName().length() + 1; // </nodename>
} else if (n instanceof ASTHtmlComment) {
nextIndex += 4 + 3; // <!-- and -->

View File

@ -21,4 +21,13 @@ class HtmlCpdLexerTest extends CpdTextComparisonTest {
doTest("SimpleHtmlFile");
}
@Test
void invalidHtml() {
doTest("InvalidHtml");
}
@Test
void metaTag() {
doTest("MetaTag");
}
}

View File

@ -0,0 +1,7 @@
<!doctype html>
<html lang="en">
<body>
<!-- missing closing tag for div -->
<div class='wrapper'>
</body>
</html>

View File

@ -0,0 +1,22 @@
[Image] or [Truncated image[ Bcol Ecol
L1
[#document] 1 8
[#doctype] 1 15
[\n] 16 16
L2
[html] 1 7
[\n] 17 17
L3
[body] 1 7
[\n] 7 7
L4
[#comment] 1 36
[\n] 37 37
L5
[div] 1 22
[\n] 22 22
L6
[\n] 8 8
L7
[\n] 8 8
EOF

View File

@ -0,0 +1,9 @@
<!doctype html>
<html lang="en">
<head>
<!-- meta tag doesn't have a closing tag and this is valid -->
<meta charset="UTF-8">
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,27 @@
[Image] or [Truncated image[ Bcol Ecol
L1
[#document] 1 8
[#doctype] 1 15
[\n] 16 16
L2
[html] 1 7
[\n] 17 17
L3
[head] 1 7
[\n ] 7 4
L4
[#comment] 5 66
[\n ] 67 4
L5
[meta] 5 27
[\n] 27 27
L6
[\n] 8 8
L7
[body] 1 7
[\n] 7 7
L8
[\n] 8 8
L9
[\n] 8 8
EOF

View File

@ -240,6 +240,12 @@ public class InsufficientStringBufferDeclarationRule extends AbstractJavaRulecha
private int calculateExpression(ASTExpression expression) {
Object value = expression.getConstValue();
return value == null ? State.UNKNOWN_CAPACITY : (Integer) value;
if (value == null) {
return State.UNKNOWN_CAPACITY;
}
if (value instanceof Character) {
return (Character) value;
}
return (Integer) value;
}
}

View File

@ -335,6 +335,13 @@ final class ExprCheckHelper {
checker.checkExprConstraint(infCtx, capture(r2), r);
}
completeMethodRefInference(mref, nonWildcard, fun, exactMethod, true);
} else if (TypeOps.isUnresolved(mref.getTypeToSearch())) {
// Then this is neither an exact nor inexact method ref,
// we just don't know what it is.
// The return values of the mref are assimilated to an (*unknown*) type.
checker.checkExprConstraint(infCtx, ts.UNKNOWN, fun.getReturnType());
completeMethodRefInference(mref, nonWildcard, fun, ts.UNRESOLVED_METHOD, false);
} else {
// Otherwise, the method reference is inexact, and:
@ -552,8 +559,15 @@ final class ExprCheckHelper {
// finally, add bounds
if (result != ts.NO_TYPE) {
Set<InferenceVar> 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 +576,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;
}
}

View File

@ -227,7 +227,6 @@ final class ExprOps {
}
} else {
JClassType enclosing = mref.getEnclosingType();
accessible = mref.getTypeToSearch()
.streamMethods(TypeOps.accessibleMethodFilter(mref.getMethodName(), enclosing.getSymbol()))
.collect(OverloadSet.collectMostSpecific(enclosing));

View File

@ -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

View File

@ -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<InstantiationListener, Set<InferenceVar>> instantiationListeners = new HashMap<>();
// explicit dependencies between variables for graph building
private final Map<InferenceVar, Set<InferenceVar>> 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<InferenceVar> freeVars = new LinkedHashSet<>();
private final Set<InferenceVar> 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<? extends InferenceVar> from, Set<? extends InferenceVar> dependencies) {
if (from.isEmpty()) {
return;
}
Set<InferenceVar> 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<InferenceVar, Set<InferenceVar>> getInstantiationDependencies() {
return instantiationConstraints;
}
void addInstantiationListener(Set<? extends JTypeMirror> relevantTypes, InstantiationListener listener) {
Set<InferenceVar> 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,7 +504,28 @@ final class InferenceContext {
solve(new GraphWalk(var));
}
private boolean solve(Supplier<VarWalkStrategy> 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) {
graphWasChanged = false;
incorporate();
while (walker.hasNext()) {
@ -470,6 +536,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<ReductionStep> wave : ReductionStep.WAVES) {
if (solveBatchProgressed(varsToSolve, wave)) {
@ -481,7 +553,7 @@ final class InferenceContext {
}
}
}
return freeVars.isEmpty();
return true;
}
/**

View File

@ -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;
@ -21,6 +22,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.internal.JavaLanguageProperties;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.types.JMethodSig;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
@ -33,6 +35,12 @@ import net.sourceforge.pmd.util.StringUtil;
/**
* A strategy to log the execution traces of {@link Infer}.
* The default does nothing, so the logger calls can be optimized out
* at runtime, while not having to check that logging is enabled at the
* call sites.
*
* <p>To enable logging for the CLI, use the language property ({@link JavaLanguageProperties})
* {@code xTypeInferenceLogging}. From tests, see {@code JavaParsingHelper#logTypeInferenceVerbose()}.
*/
@SuppressWarnings("PMD.UncommentedEmptyMethodBody")
public interface TypeInferenceLogger {
@ -61,7 +69,9 @@ 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 +91,8 @@ public interface TypeInferenceLogger {
default void propagateAndAbort(InferenceContext context, InferenceContext parent) { }
default void contextDependenciesChanged(InferenceContext ctx) { }
// ivar events
@ -90,6 +102,8 @@ public interface TypeInferenceLogger {
default void ivarInstantiated(InferenceContext ctx, InferenceVar var, JTypeMirror inst) { }
default void ivarDependencyRegistered(InferenceContext ctx, InferenceVar var, Set<InferenceVar> deps) { }
/**
* Log that the instantiation of the method type m for the given
@ -136,9 +150,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 +193,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 +220,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 +359,7 @@ public interface TypeInferenceLogger {
class VerboseLogger extends SimpleLogger {
private final Deque<Integer> marks = new ArrayDeque<>();
private final Deque<String> marks = new ArrayDeque<>();
public VerboseLogger(PrintStream out) {
super(out);
@ -343,16 +367,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 +393,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 +434,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 +482,16 @@ public interface TypeInferenceLogger {
println(addCtxInfo(ctx, "Ivar instantiated") + color(var + " := ", ANSI_BLUE) + colorIvars(inst));
}
@Override
public void ivarDependencyRegistered(InferenceContext ctx, InferenceVar var, Set<InferenceVar> 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());
}

View File

@ -90,6 +90,14 @@ interface VarWalkStrategy extends Iterator<Set<InferenceVar>> {
}
}
ctx.getInstantiationDependencies().forEach((ivar, deps) -> {
Vertex<InferenceVar> vertex = graph.addLeaf(ivar);
for (InferenceVar dep : deps) {
Vertex<InferenceVar> target = graph.addLeaf(dep);
graph.addEdge(vertex, target);
}
});
// Here, "α depends on β" is modelled by an edge α -> β
// Merge strongly connected components into a "super node".

View File

@ -50,6 +50,16 @@ class TypesTreeDumpTest extends BaseTreeDumpTest {
doTest("UnnamedPatterns");
}
@Test
void testNestedLambdasAndMethodCalls() {
doTest("NestedLambdasAndMethodCalls");
}
@Test
void testUnresolvedThings() {
doTest("UnresolvedThings");
}
@Override
protected @NonNull String normalize(@NonNull String str) {
return super.normalize(str)

View File

@ -8,7 +8,10 @@ import static net.sourceforge.pmd.lang.java.types.TestUtilitiesForTypesKt.captur
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.eqBound;
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.lower;
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.upper;
import static net.sourceforge.pmd.util.CollectionUtil.setOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -19,11 +22,17 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.TypeOps;
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.IteratorUtil;
/**
*
@ -331,4 +340,88 @@ class InferenceCtxUnitTests extends BaseTypeInferenceUnitTest {
assertThat(a, hasBoundsExactly(upper(ts.BOOLEAN.box())));
}
private static @NotNull List<Set<InferenceVar>> createBatchSetsFromGraph(InferenceContext ctx) {
GraphWalk graphWalk = new GraphWalk(ctx, false);
List<Set<InferenceVar>> batches = IteratorUtil.toList(graphWalk);
return batches;
}
@Test
void testGraphBuilding() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
// no dependency: unordered
assertThat(batches, containsInAnyOrder(setOf(a), setOf(b)));
}
@Test
void testGraphBuildingWithDependency() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// a -> b
addSubtypeConstraint(ctx, a, ts.arrayType(b));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(b), setOf(a)));
}
@Test
void testGraphBuildingWithDependency2() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// a -> b
// b -> a (because of propagation)
addSubtypeConstraint(ctx, a, b);
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(b, a)));
}
@Test
void testGraphBuildingWithExtraDependency() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// b -> a
ctx.addInstantiationDependencies(setOf(b), setOf(a));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(a), setOf(b)));
}
@Test
void testGraphBuildingWithDependencyCycle() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
InferenceVar c = newIvar(ctx);
// a -> b, b -> a,
// a -> c, b -> c
a.addBound(BoundKind.UPPER, b);
a.addBound(BoundKind.EQ, listType(c));
b.addBound(BoundKind.LOWER, a);
b.addBound(BoundKind.LOWER, listType(c));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(c), setOf(b, a)));
}
}

View File

@ -21,6 +21,7 @@ fun JavaNode.declaredMethodSignatures(): List<JMethodSig> = methodDeclarations()
fun JavaNode.methodCalls(): DescendantNodeStream<ASTMethodCall> = descendants(ASTMethodCall::class.java)
fun JavaNode.firstMethodCall() = methodCalls().crossFindBoundaries().firstOrThrow()
fun JavaNode.firstMethodCall(name: String) = methodCalls().crossFindBoundaries().filter { it.methodName == name }.firstOrThrow()
fun JavaNode.ctorCalls(): DescendantNodeStream<ASTConstructorCall> = descendants(ASTConstructorCall::class.java)
fun JavaNode.firstCtorCall() = ctorCalls().crossFindBoundaries().firstOrThrow()

View File

@ -664,4 +664,42 @@ class C {
fooToInt.referencedMethod.symbol shouldBe toIntFun
}
}
parserTest("Type inference should not resolve UNKNOWN bounded types to Object #5329") {
val (acu, _) = parser.parseWithTypeInferenceSpy(
"""
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
class Foo {
public Item methodA(List<Item> loads) {
List<SummaryDto.ItemDto> items = new ArrayList<>();
loads.stream()
// Here this collect call should have type
// Map<(*unknown*), List<*Item>>
// ie, key is unknown, not Object.
.collect(Collectors.groupingBy(Item::getValue))
.forEach((a, b) -> items.add(buildItem(a, b)));
}
private SummaryDto.ItemDto buildItem(BigDecimal a, List<Item> b) {
return SummaryDto.ItemDto.builder().build();
}
}
"""
)
val collect = acu.firstMethodCall("collect")
val buildItem = acu.firstMethodCall("buildItem")
val (_, buildItemDecl) = acu.methodDeclarations().toList { it.symbol }
val (itemT) = acu.descendants(ASTClassType::class.java).toList { it.typeMirror }
acu.withTypeDsl {
collect shouldHaveType java.util.Map::class[ts.UNKNOWN, java.util.List::class[itemT]]
buildItem.methodType.symbol shouldBe buildItemDecl
}
}
})

View File

@ -2134,4 +2134,88 @@ public class ObtainViaTest {
}
]]></code>
</test-code>
<test-code>
<description>#5324 UnusedPrivateMethod with method reference</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
package org.example.unusedPrivateMethod;
import static java.util.Collections.emptySet;
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
Library library = new Library(emptySet());
Map<String, Map<String, String>> map = new Main().run(library);
System.out.println(map);
}
private Map<String, Map<String, String>> 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<String, Map<String, String>> reduceBooksAndLenderStatusByLender(
Map<String, Map<String, String>> previousMap,
Map<String, Map<String, String>> nextMap
) {
previousMap.putAll(nextMap);
return previousMap;
}
}
record Lender(String name, String status) {}
record Book(String title, Collection<Lender> lenders) {}
record Library(Collection<Book> books) {}
]]></code>
</test-code>
<test-code>
<description>#5324 UnusedPrivateMethod with unresolved types</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
public User methodA() {
val user = userOpt.orElseGet(() -> {
try {
return registerUser(email, firstName, lastName);
} catch (Exception e) {
throw new IllegalStateException("Failed to register user for " + email, e);
}
});
// ...
return user;
}
private User registerUser(String email, String firstName, String lastName) throws Exception {
// register user logic here...
}
}
]]></code>
</test-code>
<test-code>
<description>#5329 UnusedPrivateMethod with unresolved types</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
public User methodA() {
List<SummaryDto.ItemDto> items = new ArrayList<>();
loads.stream()
.collect(Collectors.groupingBy(Item::getValue))
.forEach((a, b) -> items.add(buildItem(a, b)));
}
private SummaryDto.ItemDto buildItem(BigDecimal a, List<Item> b) {
return SummaryDto.ItemDto.builder().build();
}
}
]]></code>
</test-code>
</test-data>

View File

@ -1419,4 +1419,23 @@ public class LiteralExpression {
}
]]></code>
</test-code>
<test-code>
<description>#5314 [java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class StringBufferInstantiationWithCharTest {
public void example01() {
// misleading instantiation, these buffers
// are actually sized to 99 characters long
StringBuffer sb1 = new StringBuffer('c');
StringBuilder sb2 = new StringBuilder('c');
// in these forms, just single characters are allocated
StringBuffer sb3 = new StringBuffer("c");
StringBuilder sb4 = new StringBuilder("c");
}
}
]]></code>
</test-code>
</test-data>

View File

@ -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<String, Map<String, String>> map = new Main().run(library);
System.out.println(map);
}
private Map<String, Map<String, String>> 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<String, Map<String, String>> reduceBooksAndLenderStatusByLender(
Map<String, Map<String, String>> previousMap,
Map<String, Map<String, String>> nextMap
) {
previousMap.putAll(nextMap);
return previousMap;
}
}
record Lender(String name, String status) {}
record Book(String title, Collection<Lender> lenders) {}
record Library(Collection<Book> books) {}

View File

@ -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.Book>) -> 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.<T> emptySet() -> java.util.Set<org.example.unusedPrivateMethod.Book>", @MethodName = "emptySet", @TypeMirror = "java.util.Set<org.example.unusedPrivateMethod.Book>", @Unchecked = false, @VarargsCall = false]
| | | +- ArgumentList[]
| | +- LocalVariableDeclaration[]
| | | +- ModifierList[]
| | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | | +- TypeArguments[]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.lang.String>"]
| | | | +- TypeArguments[]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | +- VariableDeclarator[]
| | | +- VariableId[@Name = "map", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | +- 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<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| +- MethodDeclaration[@Name = "run"]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.lang.String>"]
| | | +- 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<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>.orElse(java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>) -> java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>", @MethodName = "orElse", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>", @Unchecked = false, @VarargsCall = false]
| | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>.reduce(java.util.function.BinaryOperator<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>) -> java.util.Optional<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @MethodName = "reduce", @TypeMirror = "java.util.Optional<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @Unchecked = false, @VarargsCall = false]
| | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<org.example.unusedPrivateMethod.Book>.<R> map(java.util.function.Function<? super org.example.unusedPrivateMethod.Book, ? extends java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>) -> java.util.stream.Stream<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @MethodName = "map", @TypeMirror = "java.util.stream.Stream<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @Unchecked = false, @VarargsCall = false]
| | | | +- MethodCall[@Failed = false, @Function = "java.util.Collection<org.example.unusedPrivateMethod.Book>.stream() -> java.util.stream.Stream<org.example.unusedPrivateMethod.Book>", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream<org.example.unusedPrivateMethod.Book>", @Unchecked = false, @VarargsCall = false]
| | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Library.books() -> java.util.Collection<org.example.unusedPrivateMethod.Book>", @MethodName = "books", @TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Book>", @Unchecked = false, @VarargsCall = false]
| | | | | | +- VariableAccess[@Name = "library", @TypeMirror = "org.example.unusedPrivateMethod.Library"]
| | | | | | +- ArgumentList[]
| | | | | +- ArgumentList[]
| | | | +- ArgumentList[]
| | | | +- LambdaExpression[@TypeMirror = "java.util.function.Function<org.example.unusedPrivateMethod.Book, java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>"]
| | | | +- LambdaParameterList[]
| | | | | +- LambdaParameter[@TypeMirror = "org.example.unusedPrivateMethod.Book"]
| | | | | +- ModifierList[]
| | | | | +- VariableId[@Name = "book", @TypeMirror = "org.example.unusedPrivateMethod.Book"]
| | | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<org.example.unusedPrivateMethod.Lender>.<R, A> collect(java.util.stream.Collector<? super org.example.unusedPrivateMethod.Lender, java.lang.Object, java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>) -> java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>", @MethodName = "collect", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>", @Unchecked = false, @VarargsCall = false]
| | | | +- MethodCall[@Failed = false, @Function = "java.util.Collection<org.example.unusedPrivateMethod.Lender>.stream() -> java.util.stream.Stream<org.example.unusedPrivateMethod.Lender>", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream<org.example.unusedPrivateMethod.Lender>", @Unchecked = false, @VarargsCall = false]
| | | | | +- MethodCall[@Failed = false, @Function = "org.example.unusedPrivateMethod.Book.lenders() -> java.util.Collection<org.example.unusedPrivateMethod.Lender>", @MethodName = "lenders", @TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Lender>", @Unchecked = false, @VarargsCall = false]
| | | | | | +- VariableAccess[@Name = "book", @TypeMirror = "org.example.unusedPrivateMethod.Book"]
| | | | | | +- ArgumentList[]
| | | | | +- ArgumentList[]
| | | | +- ArgumentList[]
| | | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors.<T, K, U> toMap(java.util.function.Function<? super org.example.unusedPrivateMethod.Lender, ? extends java.lang.String>, java.util.function.Function<? super org.example.unusedPrivateMethod.Lender, ? extends java.util.Map<java.lang.String, java.lang.String>>) -> java.util.stream.Collector<org.example.unusedPrivateMethod.Lender, java.lang.Object, java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector<org.example.unusedPrivateMethod.Lender, java.lang.Object, java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>", @Unchecked = false, @VarargsCall = false]
| | | | +- TypeExpression[@TypeMirror = "java.util.stream.Collectors"]
| | | | | +- ClassType[@TypeMirror = "java.util.stream.Collectors"]
| | | | +- ArgumentList[]
| | | | +- MethodReference[@TypeMirror = "java.util.function.Function<org.example.unusedPrivateMethod.Lender, java.lang.String>"]
| | | | | +- TypeExpression[@TypeMirror = "org.example.unusedPrivateMethod.Lender"]
| | | | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Lender"]
| | | | +- LambdaExpression[@TypeMirror = "java.util.function.Function<org.example.unusedPrivateMethod.Lender, java.util.Map<java.lang.String, java.lang.String>>"]
| | | | +- LambdaParameterList[]
| | | | | +- LambdaParameter[@TypeMirror = "org.example.unusedPrivateMethod.Lender"]
| | | | | +- ModifierList[]
| | | | | +- VariableId[@Name = "lender", @TypeMirror = "org.example.unusedPrivateMethod.Lender"]
| | | | +- MethodCall[@Failed = false, @Function = "java.util.Map.<K, V> of(java.lang.String, java.lang.String) -> java.util.Map<java.lang.String, java.lang.String>", @MethodName = "of", @TypeMirror = "java.util.Map<java.lang.String, java.lang.String>", @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<java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>>"]
| | | +- ThisExpression[@TypeMirror = "org.example.unusedPrivateMethod.NestedLambdasAndMethodCalls"]
| | +- ArgumentList[]
| | +- NullLiteral[@TypeMirror = "null"]
| +- MethodDeclaration[@Name = "reduceBooksAndLenderStatusByLender"]
| +- ModifierList[]
| +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | +- TypeArguments[]
| | +- ClassType[@TypeMirror = "java.lang.String"]
| | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.lang.String>"]
| | +- TypeArguments[]
| | +- ClassType[@TypeMirror = "java.lang.String"]
| | +- ClassType[@TypeMirror = "java.lang.String"]
| +- FormalParameters[]
| | +- FormalParameter[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | +- ModifierList[]
| | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | | +- TypeArguments[]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.lang.String>"]
| | | | +- TypeArguments[]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | +- VariableId[@Name = "previousMap", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | +- FormalParameter[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | +- ClassType[@TypeMirror = "java.util.Map<java.lang.String, java.lang.String>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "java.lang.String"]
| | | +- ClassType[@TypeMirror = "java.lang.String"]
| | +- VariableId[@Name = "nextMap", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| +- Block[]
| +- ExpressionStatement[]
| | +- MethodCall[@Failed = false, @Function = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>.putAll(java.util.Map<? extends java.lang.String, ? extends java.util.Map<java.lang.String, java.lang.String>>) -> void", @MethodName = "putAll", @TypeMirror = "void", @Unchecked = false, @VarargsCall = false]
| | +- VariableAccess[@Name = "previousMap", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| | +- ArgumentList[]
| | +- VariableAccess[@Name = "nextMap", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
| +- ReturnStatement[]
| +- VariableAccess[@Name = "previousMap", @TypeMirror = "java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.String>>"]
+- 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<org.example.unusedPrivateMethod.Lender>"]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Lender>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Lender"]
| | +- VariableId[@Name = "lenders", @TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Lender>"]
| +- RecordBody[]
+- RecordDeclaration[@TypeMirror = "org.example.unusedPrivateMethod.Library"]
+- ModifierList[]
+- RecordComponentList[]
| +- RecordComponent[@TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Book>"]
| +- ModifierList[]
| +- ClassType[@TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Book>"]
| | +- TypeArguments[]
| | +- ClassType[@TypeMirror = "org.example.unusedPrivateMethod.Book"]
| +- VariableId[@Name = "books", @TypeMirror = "java.util.Collection<org.example.unusedPrivateMethod.Book>"]
+- RecordBody[]

View File

@ -583,7 +583,7 @@
| +- ArgumentList[]
| +- StringLiteral[@TypeMirror = "java.lang.String"]
+- ExpressionStatement[]
+- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<java.lang.String>.<R, A> collect(java.util.stream.Collector<? super java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.Object>>) -> java.util.Map<java.lang.Object, java.lang.Object>", @MethodName = "collect", @TypeMirror = "java.util.Map<java.lang.Object, java.lang.Object>", @Unchecked = false, @VarargsCall = false]
+- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<java.lang.String>.<R, A> collect(java.util.stream.Collector<? super java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.String>>) -> java.util.Map<java.lang.Object, java.lang.String>", @MethodName = "collect", @TypeMirror = "java.util.Map<java.lang.Object, java.lang.String>", @Unchecked = false, @VarargsCall = false]
+- MethodCall[@Failed = false, @Function = "java.util.Collection<java.lang.String>.stream() -> java.util.stream.Stream<java.lang.String>", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream<java.lang.String>", @Unchecked = false, @VarargsCall = false]
| +- MethodCall[@Failed = false, @Function = "java.util.List.<E> of(java.lang.String, java.lang.String) -> java.util.List<java.lang.String>", @MethodName = "of", @TypeMirror = "java.util.List<java.lang.String>", @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.<T, K, U> toMap(java.util.function.Function<? super java.lang.String, ?>, java.util.function.Function<? super java.lang.String, ?>) -> java.util.stream.Collector<java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.Object>>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector<java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.Object>>", @Unchecked = false, @VarargsCall = false]
+- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors.<T, K, U> toMap(java.util.function.Function<? super java.lang.String, ?>, java.util.function.Function<? super java.lang.String, ? extends java.lang.String>) -> java.util.stream.Collector<java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.String>>", @MethodName = "toMap", @TypeMirror = "java.util.stream.Collector<java.lang.String, java.lang.Object, java.util.Map<java.lang.Object, java.lang.String>>", @Unchecked = false, @VarargsCall = false]
+- TypeExpression[@TypeMirror = "java.util.stream.Collectors"]
| +- ClassType[@TypeMirror = "java.util.stream.Collectors"]
+- ArgumentList[]
+- MethodReference[@TypeMirror = "java.util.function.Function<java.lang.String, java.lang.Object>"]
| +- TypeExpression[@TypeMirror = "java.lang.String"]
| +- ClassType[@TypeMirror = "java.lang.String"]
+- LambdaExpression[@TypeMirror = "java.util.function.Function<java.lang.String, java.lang.Object>"]
+- LambdaExpression[@TypeMirror = "java.util.function.Function<java.lang.String, java.lang.String>"]
+- LambdaParameterList[]
| +- LambdaParameter[@TypeMirror = "java.lang.String"]
| +- ModifierList[]

Some files were not shown because too many files have changed in this diff Show More