Receiver type given a possibly unqualified PsiMethodCallExpression?

Given a PsiMethodCallExpression, how can I discover the actual static receiver type?

 

I don't want to do resolveMethod.type, because that will give me the class within which the method was declared.

 

Is there a utility function that I can use for this?

 

Or can/should I use receiverType from javaUCallExpressions.kt for this purpose?

6 comments
Comment actions Permalink
Official comment

Hi Christian,

what is "actual static receiver type", e.g. if the method is instance method?

I believe that you need to check if `call.getMethodExpression().getQualifierExpression()` is `null`. If yes, then the type corresponds to the method's containing class; if not, then you need to call `getType()` on the qualifier. It's effectively what is done in UAST util but I'd rather suggest to write this piece manually as the api is not stable yet.

Anna

 

Comment actions Permalink

Hi Anna

By "actual static receiver type", I mean the receiver type that is known through static analysis. I probably should have just said the "receiver type" :)

For example,

class Entity { deleteEntity() }

class Student extends Entity { someMethod() { deleteEntity() }

 

At the call site of deleteEntity, the receiver type is known to be "at least" of type Student.

Sure, in the case of qualified method calls, this is easy to accomplish via the qualifierExpression.

However, in the case of unqualified method calls, this method may be inherited from the parent class.

Further, I might be accessing this within a lambda or from within a nested class, so just looking up the method that contains the method call in question might not be enough, right?

 

Cheers

Chris

0
Comment actions Permalink

If you resolve a method call and call `getContainingClass()` it will return a class where the method is defined and thus the receiver type. Or do you mean that you need the current "this" of the call? If so, please explain what you are going to do with this type 

Anna

0
Comment actions Permalink

Exactly, I want to know what the "current this" is,.

For all for all these delete() calls, I want to find out that the call will apply to the Student object.

class Scratch {
public static void main(String[] args) {

}

static class Entity {
public void delete() {

}

}

class Student extends Entity {
private void instanceMethod() {
delete();
Runnable runnable = () -> {
delete();
};

Runnable runnable1 = new Runnable() {
@Override
public void run() {
delete();
}
};
}
}

}

 

 

 

 

Live plugin experiments

 

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil

import static liveplugin.PluginUtil.registerAction
import static liveplugin.PluginUtil.*


registerAction("Get Receiver Type") { AnActionEvent event ->
def editor = currentEditorIn(currentProjectInFrame())
def offset = editor.getCaretModel().getOffset()
def psiElement = PsiUtil.getElementAtOffset(currentPsiFileIn(currentProjectInFrame()), offset)
def parent = PsiTreeUtil.getParentOfType(psiElement, PsiMethodCallExpression.class)
if (parent == null) return
show("Qualifier" + parent.methodExpression.qualifierExpression)
show("Containing class" + parent.resolveMethod().containingClass)
show("Method Expression" + parent.methodExpression)
show("methodExpression.type" + parent.methodExpression.type)
}

 

 

> If you resolve a method call and call `getContainingClass()` it will return a class where the method is defined and thus the receiver type

 

Yes, that's why I wrote "I don't want to resolveMethod.wrongThing *because that will give me the class within which the method was declared.*

 

Does IntelliJ not have a utility function to determine the type of receiver for unqualified calls? Or is that why call hierarchy doesn't take it into account? :)

 

 

0
Comment actions Permalink

This seems to work on the examples I threw at it:

 

I simply walk to parents of the node and do an inheritance check. Is there no built in functionality for this?

Also, is there no "isSubtypeOf" check, because isInheritor isn't reflective and I need to do an equality check beforehand.

Cheers

Chris

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import javassist.expr.MethodCall

import static liveplugin.PluginUtil.registerAction
import static liveplugin.PluginUtil.*

def getReceiverHeuristic(PsiMethodCallExpression methodCallExpression) {
def method = methodCallExpression.resolveMethod()
def targetType = method.getContainingClass()
show("Target type: " + targetType)

PsiElement candidate = methodCallExpression

while (candidate.parent != null) {
candidate = candidate.parent
if (candidate instanceof PsiClass) {
show("Candidate: " + candidate)
if (candidate == targetType) {
return candidate
}
if (candidate.isInheritor(targetType, true)) {
return candidate
}
}
}
}

registerAction("Get Receiver Type") { AnActionEvent event ->
def editor = currentEditorIn(currentProjectInFrame())
def offset = editor.getCaretModel().getOffset()
def psiElement = PsiUtil.getElementAtOffset(currentPsiFileIn(currentProjectInFrame()), offset)
def parent = PsiTreeUtil.getParentOfType(psiElement, PsiMethodCallExpression.class)
if (parent == null) return
show("Qualifier" + parent.methodExpression.qualifierExpression)
show("Containing class" + parent.resolveMethod().containingClass)
show("Method Expression" + parent.methodExpression)
show("methodExpression.resolve" + parent.methodExpression.resolve())
show("methodExpression.type" + parent.methodExpression.type)
show("Heuristic" + getReceiverHeuristic(parent))
}


 

 

class Scratch {
public static void main(String[] args) {

}

static class Entity {
/**
* Entity
*/
public void delete() {

}

}

class Student extends Entity {
private void instanceMethod() {
delete();
Runnable runnable = () -> {
delete();
};

Runnable runnable1 = new Runnable() {
@Override
public void run() {
delete();
}

};

Runnable runnable2 = new Runnable() {
@Override
public void run() {
delete();
}

public void delete() {

}

};

WithDefaultMethod abc = () -> {
delete();

};

WithDefaultMethod abc2 = new WithDefaultMethod() {
@Override
public void forLambda() {
delete();
{
if (false) {

}
}
}
};
}
}

interface WithDefaultMethod {
void forLambda();

/**
* Default
*/
default void delete() {
}
}
}

0
Comment actions Permalink

Try to look at `com.intellij.psi.util.InheritanceUtil#findEnclosingInstanceInScope` where you find class by `call.resolveMethod().getContainingClass()`

0

Please sign in to leave a comment.