suppressMap = new HashMap<>();
diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexCommentTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexCommentTest.java
index 728cce6253..6e847170bf 100644
--- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexCommentTest.java
+++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexCommentTest.java
@@ -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());
+ }
}
diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexLexerTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexLexerTest.java
index 67c6706f29..22104ac401 100644
--- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexLexerTest.java
+++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexLexerTest.java
@@ -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;
+ }
+ }
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java
index a6fabf91a2..5acd063c15 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java
@@ -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;
}
}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java
index dc8955fcb0..f6521426e2 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprCheckHelper.java
@@ -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:
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java
index 8ea1cee3da..a04c91c55f 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/ExprOps.java
@@ -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));
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java
index 73b843439f..8cc251fa0e 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceContext.java
@@ -525,6 +525,7 @@ final class InferenceContext {
* we try again to make progress.
*/
private boolean solve(VarWalkStrategy walker) {
+ graphWasChanged = false;
incorporate();
while (walker.hasNext()) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java
index 2c9cfff9ef..cb7fe3e6d3 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/types/internal/infer/TypeInferenceLogger.java
@@ -22,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;
@@ -34,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.
+ *
+ * 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 {
@@ -64,8 +71,7 @@ public interface TypeInferenceLogger {
default void applicabilityTest(InferenceContext ctx) { }
- default void finishApplicabilityTest() {
- }
+ default void finishApplicabilityTest() { }
default void startArgsChecks() { }
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java
index 9b8baaeca6..50d22c663d 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/TypesTreeDumpTest.java
@@ -55,6 +55,11 @@ class TypesTreeDumpTest extends BaseTreeDumpTest {
doTest("NestedLambdasAndMethodCalls");
}
+ @Test
+ void testUnresolvedThings() {
+ doTest("UnresolvedThings");
+ }
+
@Override
protected @NonNull String normalize(@NonNull String str) {
return super.normalize(str)
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceCtxUnitTests.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceCtxUnitTests.java
index 5e33c6dd01..ba1d834261 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceCtxUnitTests.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/types/internal/infer/InferenceCtxUnitTests.java
@@ -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> createBatchSetsFromGraph(InferenceContext ctx) {
+ GraphWalk graphWalk = new GraphWalk(ctx, false);
+ List> batches = IteratorUtil.toList(graphWalk);
+ return batches;
+ }
+
+ @Test
+ void testGraphBuilding() {
+ InferenceContext ctx = emptyCtx();
+ InferenceVar a = newIvar(ctx);
+ InferenceVar b = newIvar(ctx);
+
+ List> 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> 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> 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> 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> batches = createBatchSetsFromGraph(ctx);
+
+ assertThat(batches, contains(setOf(c), setOf(b, a)));
+ }
+
}
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/AstTestUtil.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/AstTestUtil.kt
index 1545c93f7e..e5c04d1ac5 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/AstTestUtil.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/AstTestUtil.kt
@@ -21,6 +21,7 @@ fun JavaNode.declaredMethodSignatures(): List = methodDeclarations()
fun JavaNode.methodCalls(): DescendantNodeStream = 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 = descendants(ASTConstructorCall::class.java)
fun JavaNode.firstCtorCall() = ctorCalls().crossFindBoundaries().firstOrThrow()
diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
index 24d7ec3c04..45c13cf569 100644
--- a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
+++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/types/internal/infer/UnresolvedTypesRecoveryTest.kt
@@ -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- loads) {
+ List 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
- 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
+ }
+ }
})
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
index 2a743036a6..1a6dd1488d 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml
@@ -2177,4 +2177,45 @@ public class ObtainViaTest {
record Library(Collection books) {}
]]>
+
+ #5324 UnusedPrivateMethod with unresolved types
+ 0
+
{
+ 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...
+ }
+ }
+ ]]>
+
+
+ #5329 UnusedPrivateMethod with unresolved types
+ 0
+ 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- b) {
+ return SummaryDto.ItemDto.builder().build();
+ }
+ }
+ ]]>
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml
index ec267fcfcc..bd5d364eb1 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml
@@ -1419,4 +1419,23 @@ public class LiteralExpression {
}
]]>
+
+
+ #5314 [java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters
+ 0
+
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.java
new file mode 100644
index 0000000000..83eb9d62b4
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.java
@@ -0,0 +1,16 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
+class Foo {
+ public User methodA(List- loads) {
+ List 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
- b) {
+ return SummaryDto.ItemDto.builder().build();
+ }
+}
\ No newline at end of file
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.txt b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.txt
new file mode 100644
index 0000000000..32f1f2643e
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/types/dumptests/UnresolvedThings.txt
@@ -0,0 +1,80 @@
++- CompilationUnit[]
+ +- ImportDeclaration[]
+ +- ImportDeclaration[]
+ +- ImportDeclaration[]
+ +- ImportDeclaration[]
+ +- ClassDeclaration[@TypeMirror = "Foo"]
+ +- ModifierList[]
+ +- ClassBody[]
+ +- MethodDeclaration[@Name = "methodA"]
+ | +- ModifierList[]
+ | +- ClassType[@TypeMirror = "*User"]
+ | +- FormalParameters[]
+ | | +- FormalParameter[@TypeMirror = "java.util.List<*Item>"]
+ | | +- ModifierList[]
+ | | +- ClassType[@TypeMirror = "java.util.List<*Item>"]
+ | | | +- TypeArguments[]
+ | | | +- ClassType[@TypeMirror = "*Item"]
+ | | +- VariableId[@Name = "loads", @TypeMirror = "java.util.List<*Item>"]
+ | +- Block[]
+ | +- LocalVariableDeclaration[]
+ | | +- ModifierList[]
+ | | +- ClassType[@TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
+ | | | +- TypeArguments[]
+ | | | +- ClassType[@TypeMirror = "*SummaryDto.ItemDto"]
+ | | +- VariableDeclarator[]
+ | | +- VariableId[@Name = "items", @TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
+ | | +- ConstructorCall[@Failed = false, @Function = "java.util.ArrayList<*SummaryDto.ItemDto>.new() -> java.util.ArrayList<*SummaryDto.ItemDto>", @MethodName = "new", @TypeMirror = "java.util.ArrayList<*SummaryDto.ItemDto>", @Unchecked = false, @VarargsCall = false]
+ | | +- ClassType[@TypeMirror = "java.util.ArrayList"]
+ | | | +- TypeArguments[]
+ | | +- ArgumentList[]
+ | +- ExpressionStatement[]
+ | +- MethodCall[@Failed = false, @Function = "java.util.Map<(*unknown*), java.util.List<*Item>>.forEach(java.util.function.BiConsumer super (*unknown*), ? super java.util.List<*Item>>) -> void", @MethodName = "forEach", @TypeMirror = "void", @Unchecked = false, @VarargsCall = false]
+ | +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<*Item>. collect(java.util.stream.Collector super *Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>) -> java.util.Map<(*unknown*), java.util.List<*Item>>", @MethodName = "collect", @TypeMirror = "java.util.Map<(*unknown*), java.util.List<*Item>>", @Unchecked = false, @VarargsCall = false]
+ | | +- MethodCall[@Failed = false, @Function = "java.util.Collection<*Item>.stream() -> java.util.stream.Stream<*Item>", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream<*Item>", @Unchecked = false, @VarargsCall = false]
+ | | | +- VariableAccess[@Name = "loads", @TypeMirror = "java.util.List<*Item>"]
+ | | | +- ArgumentList[]
+ | | +- ArgumentList[]
+ | | +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors. groupingBy(java.util.function.Function super *Item, ? extends (*unknown*)>) -> java.util.stream.Collector<*Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>", @MethodName = "groupingBy", @TypeMirror = "java.util.stream.Collector<*Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>", @Unchecked = false, @VarargsCall = false]
+ | | +- TypeExpression[@TypeMirror = "java.util.stream.Collectors"]
+ | | | +- ClassType[@TypeMirror = "java.util.stream.Collectors"]
+ | | +- ArgumentList[]
+ | | +- MethodReference[@TypeMirror = "java.util.function.Function<*Item, (*unknown*)>"]
+ | | +- AmbiguousName[@TypeMirror = "(*unknown*)"]
+ | +- ArgumentList[]
+ | +- LambdaExpression[@TypeMirror = "java.util.function.BiConsumer<(*unknown*), java.util.List<*Item>>"]
+ | +- LambdaParameterList[]
+ | | +- LambdaParameter[@TypeMirror = "(*unknown*)"]
+ | | | +- ModifierList[]
+ | | | +- VariableId[@Name = "a", @TypeMirror = "(*unknown*)"]
+ | | +- LambdaParameter[@TypeMirror = "java.util.List<*Item>"]
+ | | +- ModifierList[]
+ | | +- VariableId[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
+ | +- MethodCall[@Failed = false, @Function = "java.util.List<*SummaryDto.ItemDto>.add(*SummaryDto.ItemDto) -> boolean", @MethodName = "add", @TypeMirror = "boolean", @Unchecked = false, @VarargsCall = false]
+ | +- VariableAccess[@Name = "items", @TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
+ | +- ArgumentList[]
+ | +- MethodCall[@Failed = false, @Function = "Foo.buildItem(*BigDecimal, java.util.List<*Item>) -> *SummaryDto.ItemDto", @MethodName = "buildItem", @TypeMirror = "*SummaryDto.ItemDto", @Unchecked = false, @VarargsCall = false]
+ | +- ArgumentList[]
+ | +- VariableAccess[@Name = "a", @TypeMirror = "(*unknown*)"]
+ | +- VariableAccess[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
+ +- MethodDeclaration[@Name = "buildItem"]
+ +- ModifierList[]
+ +- ClassType[@TypeMirror = "*SummaryDto.ItemDto"]
+ +- FormalParameters[]
+ | +- FormalParameter[@TypeMirror = "*BigDecimal"]
+ | | +- ModifierList[]
+ | | +- ClassType[@TypeMirror = "*BigDecimal"]
+ | | +- VariableId[@Name = "a", @TypeMirror = "*BigDecimal"]
+ | +- FormalParameter[@TypeMirror = "java.util.List<*Item>"]
+ | +- ModifierList[]
+ | +- ClassType[@TypeMirror = "java.util.List<*Item>"]
+ | | +- TypeArguments[]
+ | | +- ClassType[@TypeMirror = "*Item"]
+ | +- VariableId[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
+ +- Block[]
+ +- ReturnStatement[]
+ +- MethodCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "build", @TypeMirror = "(*unknown*)", @Unchecked = false, @VarargsCall = false]
+ +- MethodCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "builder", @TypeMirror = "(*unknown*)", @Unchecked = false, @VarargsCall = false]
+ | +- AmbiguousName[@TypeMirror = "(*unknown*)"]
+ | +- ArgumentList[]
+ +- ArgumentList[]