Psi Type for generics
Hi,
I have a class with class-level generic. Let's say class A <T extends Number>. Inside the class there is a method: void m(T num). After converting "num" type into PsiType I'm getting PsiClassReferenceType.
But this PsiClassReferenceType cannot be used later. IDEA gives me an error: expected T but got Long. So here I believe I have to build type on my own.
Now I'm looking for a way how to define a PsiType that will hold something like "<T extends Number>".
So far I found PsiWildcardType and it allows me to specify generic like this: <? extends Number>, but it would be better to keep consistency to have a type with variable name.
P.S. Or maybe there is a way how to use PsiClassReferenceType properly so that IDEA will not give me this error of types mismatch.
Please sign in to leave a comment.
Hi Alexey,
if you want to use the type of expression, you just call PsiExpression#getType(); if you need a type of parameter, the PsiVariable#getType(). You don't need to create types yourself unless you are sure you need them.
Please explain what you do in details when you get an error about incompatible types: expected T but got Long. My first thought is that you use specialization A<Long> and the compares type T with exact type Long but if you have a specialization, you need to substitute T with current value: PsiClassType [A<Long>] #resolveGenerics().getSubstitutor().substitute(exprType). Hope this makes sense.
Anna
Hi Anna,
I would like to begin with the following piece of code https://gist.github.com/ice-pro/faa81fc7dcd9386a19eb66b106e0a907.
To have it compilable you need this dependency https://projectlombok.org/mavenrepo/.
To have it rendered properly in IDE you need to install Lombok plugin for IDEA on top.
And after that, you can see that IDEA gives me an error that I'm trying to fix.
The error itself appears inside the brackets related to "num" method.
It says that "T cannot be applied to long".
Here is how this code looks like after compilation: https://gist.github.com/ice-pro/9760508f3e139ee34e82c63f89896569
I'm trying to give a proper hint to IDEA about parameter type of "num" method.
As an input I have "PsiVariable" of this method: "T num".
And now I need to build a "PsiMethod" with properly specified parameter type.
Current implementation looks like this "psiVariable.getType()" but it brings an error because it is interpreted as T type, without any additional information.
So I have to provide more suitable type, something like <T extends Number>.
I already got some progress here using the following approach:
- after calling resolve() on provided type (first check, it must be PsiClassReferenceType) I have PsiClass
- this PsiClass (first check, it must be PsiTypeParameter) can give me its superclass and this super class I can use to build PsiWildcardType
The questions here are following:
1. Approach I used is extremely slow on big files and I can see signficant performance degradation in my IDE. Even caching results of resolve() and PsiWildcardType.createExtends() methods doesn't bring much speed up. It looks like this degradation is caused by calling "resolve()" method, because I have to call it many times. But without it I cannot be sure that PsiType has PsiTypeParameter inside. Or there is a way to check this?
2. PsiWildcardType gives me <? extends Number> but not <T extends Number>. Is there any way to get the second option?
I appreciate for help.
Regards,
Alexey
Please explain what plugin you are working on or what bug in lombok is it? The questions unfortunately don't make much sense to me yet, very straightforward answers:
1. Resolve is cached inside IDEA itself so it shouldn't influence the performance unless you resolve on newly generated code each time.
2. <T extends Number> is a declaration of a new type parameter, it's not a type. You can create a new type parameter but I doubt that it's what you want here.
Anna
I am working on lombok plugin itself.
Here are two related bugs:
https://github.com/mplushnikov/lombok-intellij-plugin/issues/306
https://github.com/mplushnikov/lombok-intellij-plugin/issues/127
Here is related line of code https://github.com/mplushnikov/lombok-intellij-plugin/blob/dc820d5bf636456fdddf61ff3f0b6fdb3f37e0c2/src/main/java/de/plushnikov/intellij/plugin/processor/handler/singular/NonSingularHandler.java#L44
Expression "psiVariable.getType()" gives a hint to IDEA to use T as type but it causes an error.
I am trying to replace "psiVariable.getType()" with PsiWildcardType in a way I explained before.
There must be a type parameter in generated 'builder' method, like from the bytecode
// declaration: GenericsBuilderTest$GenericsBuilderTestBuilder<T> builder<T extends java.lang.Number>()
This type parameter should be used as type of num, not T from GenericsBuilderTest. See my issue at https://github.com/mplushnikov/lombok-intellij-plugin/issues/313.
Anna
But how to generate this type parameter?
And where from did you get the line above? I have the following:
public static <T extends Number> GenericsBuilderTest.GenericsBuilderTestBuilder<T> builder()
I found an answer for my previous question about type parameter.
I have to "resolve" type first and then if it is PsiTypeParameter then I can just use it as is.
But unfortunately I still have performance problem on my class that contains 3000 lines of different builders.
Do you have any suggestions how this can be improved?
P.S. I can see some thread.sleep invokes inside resolve method. Is there any possibility to get type parameter faster?
You need to substitute the psiVariable.getType() according to the type parameters of method builder.
Can you please give me some hints using java code?
Because it looks like my solution is quite similar to what you are proposing but still it is a bit slow.
What hints do you need?
You have the call 'builder()', you can calculate the type of the call expr.getType(). You know that it must be PsiClassType. You can call resolveGenerics() on that type and get PsiSubstitutor from that call. This substitutor describes how type parameter of GenericsBuilderTest corresponds to type parameter of 'builder' method. If you would substitute on this substitutor the type of psiVariable.getType, you should get correct results
Sorry, look like I didn't get your answer clearly.
First I did a bit more complex example, here is it: https://gist.github.com/ice-pro/a50ff75cec1e300841d724993d79e181
And the sequence of actions behind is following:
1. Detect method annotated with @Builder and retrieve the list of its parameters
2. Create "setter" methods for each parameter
3. Wrap all methods into builder method
So for example above I have as entry point method "GenericsBuilderTest(I intVal, F floatVal, String strVal)"
And for it I need to generate 3 methods.
To do this first I have a collection of PsiParameter extracted out of this method.
And then psiParameter.getType is used to specify parameter type for new method.
I tried to extract the real type behind the generic and create PsiWildcardType based on this, but it looks like it is wrong way.
So you said that I have to take each psiParameter and check if it is PsiTypeParameter and if it is then using substitutor mechanism get correct types, is it right?
If yes it means that it must be called for every parameter. And I think it could be slow.
Is there another way to do this at more early stage, for example when I got a method annotated with @Builder I can resolve correct types immediately and later they will be used as is.
1. PsiWildcardType can't be top level type, it's not denotable (4.5.1 Type Arguments of Parameterized Types of java spec), that's why I don't see any future for this attempt
2. Please explain (3.) - wrap all methods into builder method. How do you generate builder method, what signature does it have?
3. I don't say that you need to resolve PsiType to get PsiTypeParameter, PsiSubstitutor will do it for you and no, there is no performance problem here unless there are code generation for each type one ask for a type
2. I am not sure that we have to go deeper here. If you look here https://github.com/mplushnikov/lombok-intellij-plugin/blob/dc820d5bf636456fdddf61ff3f0b6fdb3f37e0c2/src/main/java/de/plushnikov/intellij/plugin/processor/clazz/builder/BuilderProcessor.java#L83
You can see code related to the creation of builder class with related methods. And only several lines below there is a code related to builder method creation.
Let's assume that we are quite isolated from other parts and have only method parameter (with generic inside) for which we have to create a corresponding method.
3. Resolving is actually what I've tried to do. And yes it looks slow for me. But can we try to make a very small example of proper type processing and further method creation.
Let's say I have a psiParameter with generic. And I need to create a method that has a parameter of the same type.
To do this I've already tried:
- to use psiParameter.getType() - it was initial implementation and it gives me an error
- to use ((PsiClassReferenceType) psiParameter.getType()).resolve() to get PsiTypeParameter and use while method creation. Here I got the problem solved but performance problem also comes into play.
Now we came to the point where we have to get rid of second solution I provided above and use substitution mechanism.
My questions are:
1. How can I actually use substitution mechanism properly? Let say I have a type psiParameter.getType() and then I can resolveGenerics() on it. It will give me ClassResolveResult. On this result I can call getSubstitutor().substitute(arg) and provide a proper "arg". What this "arg" should be?
2. Do I have to substitute every parameter type one by one? Or there is a more generic way?
Anna, can we continue discussion? I still would like to get some answers for questions in previous message. Or you need some details from my side?
1. arg can be a type of a parameter, then you'll get the type in the substitution of builder method parameters
2. You can method.getSignature(PsiSubstitutor).getParameterTypes()
I already tried to open gitHub and browse the code to check how to help you but it doesn't work and I need to checkout the plugin to get more information. Sorry, probably I would have some time for that later but I can't promise that.
Anna
Thank you for reply.
1. I am trying to understand in particular example how can I proceed here. Let's say I have PsiType called sourceType (I got it from psiParameter.getType()). And I know that it is instance of PsiClassReferenceType. Now I need to build targetType to use for my new method.
And I am trying to do the following: targetType = ((PsiClassReferenceType) sourceType).resolveGenerics().getSubstitutor().substitute(<arg>). And here <arg> is the most interesting part for me. Perhaps I can get it from sourceType, or not?
2. This is interesting. Can it be used once at the beginning to "normalize" types of parameters? By "normalize" I mean substitute types by actual values.
It yes then it could solve the problem in a more generic way. The only quetion here is argument of type PsiSubstitutor of method getSignature, how to get it?
And of course I would appreciate it you can check sources, perhaps it could simplify our discussion.
Regards,
Alexey
By the way, the HEAD version of plugin is not compilable for me. So I had to get sources of latest release here https://github.com/mplushnikov/lombok-intellij-plugin/releases/tag/releasebuild_0.13
source type looks wrong to me, it must represent the parameterization of GenericsBuilderTest. When you get the substitutor it can be used for building MethodSignature.
But isn't it too complex? Parametrisation can be either on class level or on method level.
But when we have PsiParameter we are quite isolated and can work with it.
Hi Anna,
I already tried to open GitHub and browse the code to check how to help you but it doesn't work and I need to checkout the plugin to get more information. Sorry, probably I would have some time for that later but I can't promise that.
Sorry for disturbing you again, but it would be great if we can continue this discussion. Probably you had a chance to look into code we are discussing and got more details on this problem.
Hi Anna, please let me know if you still have time to continue current discussion. Otherwise we can just close this ticket.
Hi Alexey,
this is a forum where IDEA developers answer questions on IDEA's api. I think that I answered all the questions about api which were posted here.
Sorry, but I have my own schedule and didn't have time to check Lombok yet.
Anna
Sure, I can understand this. But still due to lack of API documentation it is not clear how to proceed with the current problem properly.
Perhaps you can give me a code snippet (not just a hints on API but complete code listing would be appreciated) which can solve the problem I've asked previously:
I am trying to understand in particular example how can I proceed here. Let's say I have PsiType called sourceType (I got it from psiParameter.getType()). And I know that it is instance of PsiClassReferenceType. Now I need to build targetType to use for my new method.
And I am trying to do the following: targetType = ((PsiClassReferenceType) sourceType).resolveGenerics().getSubstitutor().substitute(<arg>). And here <arg> is the most interesting part for me. Perhaps I can get it from sourceType, or not?
Regards,
Alexey
See my pull request: https://github.com/mplushnikov/lombok-intellij-plugin/pull/329
Anna
Anna, I really appreciate your help.
The usage of API is more clear for me now, actually my solution was quite similar but in wrong place.
Moreover now I got the reason of the problem I had with performance. The logic BuilderHandler#getBuilderSubstitutor I put into NonSingularHandler#addBuilderMethod directly which is in fact wrong.
Regards,
Alexey