javaparser
javaparser copied to clipboard
Is there any way to get the implicit type arguments for a method?
In this following, assume there is a class Creator
with a static method create
that receives one type argument and one argument of that type (the value).
String myString = Creator.create("ABC");
Is there anyway for JavaParser to resolve the type arguments given to the method create
solely from Creator.create("ABC")
? It should return String
. Note that these type arguments won't necessarily align with the type on the left side, so I can't look at the left side of the variable declaration.
@ftomassetti ?
I cannot think of one on the top of my head. The resolution process has to figure out the values for the type arguments, the problem is remembering how to get those values. There is one class called MethodUsage
that is basically a combination of a MethodDeclaration
and the type parameters (both the type parameters on the method and the type parameters in containing classes/interfaces).
So the question becomes, how do we go from a MethodCallExpr
to a MethodUsage
?
In general we should make that easier (i.e., add a method for that).
For now I would use the JavaParserFacade
method named solveMethodAsUsage
. It takes a MethodCallExpr
and return a MethodUsage
.
@ftomassetti I have an instance of MethodUsage
(forgot to say that in the original question, sorry about that). When looking through the possible methods, it only allows me to get the method declaration (which would in turn allow me to get the type parameters) and the types of the arguments. It doesn't provide a method to get the type arguments. Am I missing something?
You should have a method named typeParametersMap
On Tue, 26 Mar 2019 at 02:01, Mario Ishac [email protected] wrote:
@ftomassetti https://github.com/ftomassetti I have an instance of MethodUsage (forgot to say that in the original question, sorry about that). When looking through the possible methods, it only allows me to get the method declaration (which would in turn allow me to get the type parameters) and the types of the arguments. It doesn't provide a method to get the type arguments. Am I missing something?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/javaparser/javaparser/issues/2135#issuecomment-476433497, or mute the thread https://github.com/notifications/unsubscribe-auth/AAazJhrtbz9n4B_to3k5upSB3TkH0N3bks5vaXFOgaJpZM4cFx1g .
-- Website at https://tomassetti.me GitHub https://github.com/ftomassetti
You should have a method named typeParametersMap … On Tue, 26 Mar 2019 at 02:01, Mario Ishac @.***> wrote: @ftomassetti https://github.com/ftomassetti I have an instance of MethodUsage (forgot to say that in the original question, sorry about that). When looking through the possible methods, it only allows me to get the method declaration (which would in turn allow me to get the type parameters) and the types of the arguments. It doesn't provide a method to get the type arguments. Am I missing something? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#2135 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/AAazJhrtbz9n4B_to3k5upSB3TkH0N3bks5vaXFOgaJpZM4cFx1g . -- Website at https://tomassetti.me GitHub https://github.com/ftomassetti
Sorry that I haven't responded in a while, but upon testing with this method it seems that it returns type parameter-argument mappings for type arguments explicitly used, not implicitly. A MethodUsage
corresponding to this.methodWithTypeParameters("B")
would have an empty type parameters-arguments map, even if there was an inferred type argument of String
.
Is there any way to get the implicit mappings as well?
mmm, I think so because internally we need that stuff. I do not remember how to access it and if those are exposed externally.
This is the point where we ask for help from our users, like user @TheeNinja :-)
@ftomassetti @matozoid In the process of building a unit test for the expected behavior, I discovered that the implicit type arguments are actually discovered in a MethodUsage
where the implicit type arguments serve as the type of a method argument, such as in the example I provided (method create
). I had overgeneralized the issue, the issue appears when doing things like Stream.Builder<String> builder = Stream.builder()
(the line from my application where I noticed the issue) as the method solveMethodAsUsage(String name, List<ResolvedType> argumentsTypes)
in MethodCallExprContent
only searches for implicit type arguments through the method resolveMethodTypeParameters(MethodUsage methodUsage, List<ResolvedType> actualParamTypes)
. Both of these methods receive only argument types as information (and no return type of the method) so it makes sense that they modify the type parameter-argument map solely based on that information.
I would need to pass the information of where the method is called in addition to the arguments provided in the call. This is because the type argument corresponding to the method return type is inferred from there. It would have to be inferred differently in String a = methodWithTypeParameter()
compared to methodThatTakesAString(methodWithTypeParameter())
(in both cases the inferred type would be a String
). Do you know if there is any way to access this surrounding information from an instance of MethodCallExpr
or MethodUsage
so I can make the PR?
If @ftomassetti can't remember, then you are the new expert, @TheeNinja :-)
@matozoid @ftomassetti I'm trying to become the new expert, but based on my unit tests it seems there's many more problems than just not accounting for the return type. I'll continue trying to fix them, but I've reached a little dilemma I've been stuck at for a few hours: From MethodCallExprContext
:
public Optional<MethodUsage> solveMethodAsUsage(String name, List<ResolvedType> argumentsTypes) {
if (wrappedNode.getScope().isPresent()) {
Expression scope = wrappedNode.getScope().get();
// Considers calls of the form <name>.method(), name can be class meaning that this if block considers static methods as well.
if (scope instanceof NameExpr) {
String className = ((NameExpr) scope).getName().getId();
SymbolReference<ResolvedTypeDeclaration> ref = solveType(className);
if (ref.isSolved()) {
SymbolReference<ResolvedMethodDeclaration> m = MethodResolutionLogic.solveMethodInType(ref.getCorrespondingDeclaration(), name, argumentsTypes);
if (m.isSolved()) {
MethodUsage methodUsage = new MethodUsage(m.getCorrespondingDeclaration());
methodUsage = resolveMethodTypeParametersFromExplicitList(typeSolver, methodUsage);
methodUsage = resolveMethodTypeParameters(methodUsage, argumentsTypes);
return Optional.of(methodUsage);
} else {
throw new UnsolvedSymbolException(ref.getCorrespondingDeclaration().toString(),
"Method '" + name + "' with parameterTypes " + argumentsTypes);
}
}
}
ResolvedType typeOfScope = JavaParserFacade.get(typeSolver).getType(scope);
// we can replace the parameter types from the scope into the typeParametersValues
Map<ResolvedTypeParameterDeclaration, ResolvedType> inferredTypes = new HashMap<>();
for (int i = 0; i < argumentsTypes.size(); i++) {
// by replacing types I can also find new equivalences
// for example if I replace T=U with String because I know that T=String I can derive that also U equal String
ResolvedType originalArgumentType = argumentsTypes.get(i);
ResolvedType updatedArgumentType = usingParameterTypesFromScope(typeOfScope, originalArgumentType, inferredTypes);
argumentsTypes.set(i, updatedArgumentType);
}
for (int i = 0; i < argumentsTypes.size(); i++) {
ResolvedType updatedArgumentType = applyInferredTypes(argumentsTypes.get(i), inferredTypes);
argumentsTypes.set(i, updatedArgumentType);
}
return solveMethodAsUsage(typeOfScope, name, argumentsTypes, this);
} else {
Context parentContext = getParent();
while (parentContext instanceof MethodCallExprContext || parentContext instanceof ObjectCreationContext) {
parentContext = parentContext.getParent();
}
return parentContext.solveMethodAsUsage(name, argumentsTypes);
}
}
Why is there different behaviors regarding inference of type arguments based on whether the scope is a name expression or not? If it is, it seems exception and return types are not accounted for in type argument resolution. If it is not, this line executes: solveMethodAsUsage(typeOfScope, name, argumentsTypes, this);
, and this variation of solveMethodAsUsage
seems to account for those in addition to the argument types. I'm trying to eliminate this duplicated functionality.
I'll probably have the answer to my question after some more thought, but if by change you guys know it'll make my job easier.
Oof, we need @ftomassetti for this.
I am currently facing a similar challenge: How to resolve the actual type arguments in a scenario like new HashMap<>()
?
There are various scenarios where the Java Compiler can infer the type arguments.
The discussion so far was aiming to infer the type arguments from the parameters, but this doesn't work in cases like mine. Is there a way to get the contextual type information of such a constructor call?
Is there a way to get the contextual type information of such a constructor call?
Not directly from the constructor call (i.e., ObjectInitializerExpr
) as there is not enough information available within that node, but it is usually possible to get the declared type.
For example, if the result of the object initialisation is assigned to a variable, you can resolve that variable to examine the declared type of that variable. I show this within a sample below.
@Danielku15 -- If the sample below doesn't match your use-case, could you share a small snippet of what you are trying to do please?
I am currently facing a similar challenge: How to resolve the actual type arguments in a scenario like
new HashMap<>()
?
Is it sufficient for your needs to navigate to the variable declaration, then inspect the declared type?
Here is an example showing how this might be done (caution: with defensive guards/error-checking removed -- not suitable for production code):
import java.util.HashMap;
class Scratch {
HashMap<Integer, String> map;
public Scratch() {
map = new HashMap<>();
}
}
package com.github.javaparser.symbolsolver;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.NameExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.resolution.types.ResolvedReferenceType;
import com.github.javaparser.resolution.types.ResolvedType;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.junit.jupiter.api.Test;
import java.util.List;
@Test
public void issue2315_sample__inferred_type_arguments__getting_declared_type_parameters() {
String inputText = "" +
"import java.util.HashMap;\n" +
"\n" +
"class Scratch {\n" +
" HashMap<Integer, String> map;\n" +
" public Scratch() {\n" +
" map = new HashMap<>();\n" +
" }\n" +
"}\n" +
"";
JavaParser javaParser = new JavaParser();
// Set up a minimal type solver that only looks at the classes used to run this sample.
// Configure JavaParser to use type resolution
TypeSolver typeSolver = new ReflectionTypeSolver();
JavaSymbolSolver javaSymbolSolver = new JavaSymbolSolver(typeSolver);
javaParser.getParserConfiguration()
.setSymbolResolver(javaSymbolSolver);
// Attempt parsing
ParseResult<CompilationUnit> parseResult = javaParser.parse(inputText);
// Get a usage of implicit type parameters from the input source
ObjectCreationExpr objectCreationExpr = parseResult
.getResult()
.get()
.findAll(ObjectCreationExpr.class)
.get(0);
// Initialised type details (as part of the `ObjectCreationExpr` -- i.e., `new HashMap<>()`)
ClassOrInterfaceType initialisedType = objectCreationExpr.getType();
NodeList<Type> initialisedTypeArguments = initialisedType.getTypeArguments().get();
System.out.println("initialisedType = " + initialisedType);
System.out.println("initialisedTypeArguments = " + initialisedTypeArguments);
// Get variable associated with this `ObjectCreationExpr`
NameExpr variableNameExpr = objectCreationExpr
.findAncestor(AssignExpr.class)
.get()
.getTarget()
.asNameExpr();
// Resolve the variable name, to get the declared type
ResolvedReferenceType resolvedDeclaredType = variableNameExpr
.calculateResolvedType()
.asReferenceType();
// Examine the _declared_ type parameters
List<ResolvedType> resolvedDeclaredTypeParameters = resolvedDeclaredType.typeParametersValues();
System.out.println("resolvedDeclaredTypeParameters = " + resolvedDeclaredTypeParameters);
}
}
This outputs the following -- note that the declared type parameters are resolved and shown below:
initialisedType = HashMap<>
initialisedTypeArguments = []
resolvedDeclaredTypeParameters = [ReferenceType{java.lang.Integer, typeParametersMap=TypeParametersMap{nameToValue={}}}, ReferenceType{java.lang.String, typeParametersMap=TypeParametersMap{nameToValue={}}}]
@MysterAitch This is what I basically tried and it works.. somehow. But as you can imagine: handling all scenarios is quite a big deal. I have now covered 5 cases in my code and it is sufficient for my needs, still I'm not really happy. Luckily my project is anyhow just a transpiler which is not meant to be perfect and needs manual cleanup of the generated code. The cases I covered now:
-
Map<String, String> x = new HashMap<>()
looks for a VariableDeclarator node and tries to pull the types from there. -
new Other(new HashMap<>())
looks for parent ObjectCreationExpr, then looks for the right constructor parameter definition and pulls the type arguments from there. -
Method(new HashMap<>())
looks for a parent MethodCallExpr, then looks for the right method parameter definition and pulls the type arguments from there. -
x.y = new HashMap<>()
looks for a AssignExpr, then tries to resolve type of this AssignExpr throughcalculateResolvedType()
and pulls the type arguments from there. -
return new HashMap<>()
looks for the parent MethodDeclaration and then pulls the type arguments from the return type.
This works mostly for the happy paths. But as soon there are further generic types included in this scope it starts failing.
You can look at the related code here: https://github.com/Danielku15/SigningServer/blob/77dfa94466d6f781dda8df2a3922f533add1e4d6/tools/apksig-transpiler/src/main/kotlin/CSharpAstTransformer.kt#L792-L874
(Disclaimer: it's not fully clean-code but as it is just a small tool to serve my needs on translating Java to C# code I'm fine with it 😁).