[java] Fix inference dependency issue with nested lambdas (#5325)
Merge pull request #5325 from oowekyala:typeres-fix-inference-issue
This commit is contained in:
commit
bb2782241e
@ -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
|
||||
|
||||
|
@ -552,8 +552,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 +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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,6 +504,26 @@ 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) {
|
||||
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<ReductionStep> wave : ReductionStep.WAVES) {
|
||||
if (solveBatchProgressed(varsToSolve, wave)) {
|
||||
@ -481,7 +552,7 @@ final class InferenceContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
return freeVars.isEmpty();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<InferenceVar> 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<Integer> marks = new ArrayDeque<>();
|
||||
private final Deque<String> 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<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());
|
||||
}
|
||||
|
@ -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".
|
||||
|
@ -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)
|
||||
|
@ -2134,4 +2134,47 @@ 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-data>
|
||||
|
@ -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) {}
|
@ -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[]
|
@ -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[]
|
||||
|
Loading…
x
Reference in New Issue
Block a user