Skip to content

Commit

Permalink
Merge pull request javaparser#4128 from jlerbsc/master
Browse files Browse the repository at this point in the history
Fix: issue 3976 Issue resolving implicit generic types
  • Loading branch information
jlerbsc authored Aug 26, 2023
2 parents 065e80b + b4b7eb2 commit 8103175
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
package com.github.javaparser.symbolsolver.javaparsermodel.contexts;

import java.util.*;
import java.util.stream.Collectors;

import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
Expand All @@ -38,6 +39,7 @@
import com.github.javaparser.resolution.model.typesystem.ReferenceTypeImpl;
import com.github.javaparser.resolution.types.*;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.resolution.typeinference.LeastUpperBoundLogic;
import com.github.javaparser.utils.Pair;

public class MethodCallExprContext extends AbstractJavaParserContext<MethodCallExpr> {
Expand Down Expand Up @@ -408,14 +410,86 @@ private MethodUsage resolveMethodTypeParameters(MethodUsage methodUsage, List<Re
return methodUsage;
}

/*
* Replace formal type parameter (e.g. T) by actual type argument
* If there is more than one actual type argument for a formal type parameter
* then the type parameter list is reduced using LUB fonction.
*/
private MethodUsage replaceTypeParameter(MethodUsage methodUsage,
Map<ResolvedTypeParameterDeclaration, ResolvedType> matchedTypeParameters) {
// first group all resolved types by type variable
Map<String, Set<ResolvedType>> resolvedTypesByTypeVariable = groupResolvedTypeByTypeVariable(matchedTypeParameters);
// then reduce the list of resolved types with the least upper bound logic
Map<String, ResolvedType> reducedResolvedTypesByTypeVariable = reduceResolvedTypesByTypeVariable(resolvedTypesByTypeVariable);
// then replace resolved type by the reduced type for each type variable
convertTypesParameters(matchedTypeParameters, reducedResolvedTypesByTypeVariable);
// finally replace type parameters
for (ResolvedTypeParameterDeclaration tp : matchedTypeParameters.keySet()) {
methodUsage = methodUsage.replaceTypeParameter(tp, matchedTypeParameters.get(tp));
}
return methodUsage;
}

/*
* Update the matchedTypeParameters map from the types in reducedResolvedTypesByTypeVariable map.
*/
private void convertTypesParameters(
Map<ResolvedTypeParameterDeclaration, ResolvedType> matchedTypeParameters,
Map<String, ResolvedType> reducedResolvedTypesByTypeVariable) {
for (ResolvedTypeParameterDeclaration tp : matchedTypeParameters.keySet()) {
String typeParameterName = tp.getName();
boolean replacement = reducedResolvedTypesByTypeVariable.keySet().contains(typeParameterName);
if (replacement) {
matchedTypeParameters.put(tp, reducedResolvedTypesByTypeVariable.get(typeParameterName));
}
}
}

/*
* Group resolved type by the variable type. For example in Map.of("k0", 0, "k1",
* 1D) which is solved as static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2)
* the type variable named V that represents the type of the first and fourth parameter
* must reference v1 (Integer type) and v2 (Double type).
*/
private Map<String, Set<ResolvedType>> groupResolvedTypeByTypeVariable(Map<ResolvedTypeParameterDeclaration, ResolvedType> typeParameters) {
Map<String, Set<ResolvedType>> resolvedTypesByTypeVariable = new HashMap<>();
for (ResolvedTypeParameterDeclaration tp : typeParameters.keySet()) {
String typeParameterName = tp.getName();
boolean alreadyCollected = resolvedTypesByTypeVariable.keySet().contains(typeParameterName);
if (!alreadyCollected) {
Set<ResolvedType> resolvedTypes = findResolvedTypesByTypeVariable(typeParameterName, typeParameters);
resolvedTypesByTypeVariable.put(typeParameterName, resolvedTypes);
}
}
return resolvedTypesByTypeVariable;
}

/*
* Collect all resolved type from a type variable name
*/
private Set<ResolvedType> findResolvedTypesByTypeVariable(String typeVariableName, Map<ResolvedTypeParameterDeclaration, ResolvedType> typeParameters) {
return typeParameters.keySet().stream()
.filter(resolvedTypeParameterDeclaration -> resolvedTypeParameterDeclaration.getName().equals(typeVariableName))
.map(resolvedTypeParameterDeclaration -> typeParameters.get(resolvedTypeParameterDeclaration))
.collect(Collectors.toSet());
}

/*
* Reduce all set of resolved type with LUB
*/
private Map<String, ResolvedType> reduceResolvedTypesByTypeVariable(Map<String, Set<ResolvedType>> typeParameters) {
Map<String, ResolvedType> reducedResolvedTypesList = new HashMap<>();
for (String typeParameterName : typeParameters.keySet()) {
ResolvedType type = reduceResolvedTypesWithLub(typeParameters.get(typeParameterName));
reducedResolvedTypesList.put(typeParameterName, type);
}
return reducedResolvedTypesList;
}

private ResolvedType reduceResolvedTypesWithLub(Set<ResolvedType> resolvedTypes) {
return LeastUpperBoundLogic.of().lub(resolvedTypes);
}

private void matchTypeParameters(ResolvedType expectedType, ResolvedType actualType, Map<ResolvedTypeParameterDeclaration, ResolvedType> matchedTypeParameters) {
if (expectedType.isTypeVariable()) {
ResolvedType type = actualType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.github.javaparser.symbolsolver;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
import org.junit.jupiter.api.condition.JRE;

import com.github.javaparser.JavaParserAdapter;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.resolution.AbstractResolutionTest;

public class Issue3976Test extends AbstractResolutionTest {

@Test
@EnabledForJreRange(min = JRE.JAVA_9)
void test() {
String testCase =
"import java.util.Map;\n"
+ "public class Foo {\n"
+ " public Object m() {\n"
+ " return Map.of(\"k0\", 0, \"k1\", 1D);\n"
+ " }\n"
+ "}";

CompilationUnit cu = JavaParserAdapter.of(createParserWithResolver(defaultTypeSolver())).parse(testCase);

MethodCallExpr methodCallExpr = cu.findFirst(MethodCallExpr.class).get();

ResolvedType rt = methodCallExpr.calculateResolvedType();
assertEquals("java.util.Map<java.lang.String, java.lang.Number>", rt.describe());
}
}

0 comments on commit 8103175

Please sign in to comment.