ISanych ISanych - 24 days ago 9
Java Question

Symbols resolution in standalone IntelliJ parser

I'm trying to use IntelliJ SDK as standalone java parser and it works fine in most cases, but failing to resolve return type of generic methods.

When I debugging

resolveMethod
for
verify(mock).simpleMethod()
in next sample inside of IntelliJ:

public class ResolutionTest {

private interface IMethods {
String simpleMethod();
}

private IMethods mock;

public static <T> T verify(T m) {
return m;
}

public void test() {
verify(mock).simpleMethod();
}

}


I see return type of
verify(mock)
as
IMethods
and
simpleMethod
also resolved correctly. But in my parser return type of
verify(mock)
is
T
and
simpleMethod
resolution failing because of that. I guess I'm not register some service or extension but I cannot figure out which one.

My parser:

import com.intellij.codeInsight.ContainerProvider;
import com.intellij.codeInsight.runner.JavaMainMethodProvider;
import com.intellij.core.CoreApplicationEnvironment;
import com.intellij.core.CoreJavaFileManager;
import com.intellij.core.JavaCoreApplicationEnvironment;
import com.intellij.core.JavaCoreProjectEnvironment;
import com.intellij.mock.MockProject;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileTypes.FileTypeExtensionPoint;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.augment.TypeAnnotationModifier;
import com.intellij.psi.compiled.ClassFileDecompilers;
import com.intellij.psi.impl.JavaClassSupersImpl;
import com.intellij.psi.impl.PsiElementFinderImpl;
import com.intellij.psi.impl.PsiNameHelperImpl;
import com.intellij.psi.impl.PsiTreeChangePreprocessor;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.meta.MetaDataContributor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.BinaryFileStubBuilders;
import com.intellij.psi.util.JavaClassSupers;

import java.io.File;

public class Main {

static class Analyzer extends PsiElementVisitor {
static final Disposable disposable = () -> {
};

private static class ProjectEnvironment extends JavaCoreProjectEnvironment {
public ProjectEnvironment(Disposable parentDisposable, CoreApplicationEnvironment applicationEnvironment) {
super(parentDisposable, applicationEnvironment);
}

@Override
protected void registerJavaPsiFacade() {
JavaFileManager javaFileManager = getProject().getComponent(JavaFileManager.class);
CoreJavaFileManager coreJavaFileManager = (CoreJavaFileManager) javaFileManager;
ServiceManager.getService(getProject(), CoreJavaFileManager.class);
getProject().registerService(CoreJavaFileManager.class, coreJavaFileManager);
getProject().registerService(PsiNameHelper.class, PsiNameHelperImpl.getInstance());
PsiElementFinder finder = new PsiElementFinderImpl(getProject(), coreJavaFileManager);
ExtensionsArea area = Extensions.getArea(getProject());
area.getExtensionPoint(PsiElementFinder.EP_NAME).registerExtension(finder);
super.registerJavaPsiFacade();
}

@Override
protected void preregisterServices() {
super.preregisterServices();
ExtensionsArea area = Extensions.getArea(getProject());
CoreApplicationEnvironment.registerExtensionPoint(area, PsiTreeChangePreprocessor.EP_NAME, PsiTreeChangePreprocessor.class);
CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP_NAME, PsiElementFinder.class);
}
}

private static class ApplicationEnvironment extends JavaCoreApplicationEnvironment {

public ApplicationEnvironment(Disposable parentDisposable) {
super(parentDisposable);
myApplication.registerService(JavaClassSupers.class, new JavaClassSupersImpl());
}
}

final ApplicationEnvironment applicationEnvironment;
final ProjectEnvironment projectEnvironment;

public Analyzer() {
ExtensionsArea rootArea = Extensions.getRootArea();
CoreApplicationEnvironment.registerExtensionPoint(rootArea, BinaryFileStubBuilders.EP_NAME, FileTypeExtensionPoint.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, FileContextProvider.EP_NAME, FileContextProvider.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, MetaDataContributor.EP_NAME, MetaDataContributor.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, PsiAugmentProvider.EP_NAME, PsiAugmentProvider.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, ContainerProvider.EP_NAME, ContainerProvider.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, ClassFileDecompilers.EP_NAME, ClassFileDecompilers.Decompiler.class);
CoreApplicationEnvironment.registerExtensionPoint(rootArea, TypeAnnotationModifier.EP_NAME, TypeAnnotationModifier.class);
applicationEnvironment = new ApplicationEnvironment(disposable);
projectEnvironment = new ProjectEnvironment(disposable, applicationEnvironment);
}

public void add(final String[] args) throws Exception {
for (String arg : args) {
final VirtualFile root = applicationEnvironment.getLocalFileSystem().findFileByIoFile(new File(arg));
projectEnvironment.addSourcesToClasspath(root);
}
}


public void run() {
MockProject project = projectEnvironment.getProject();
PsiClass cls = project.getComponent(JavaFileManager.class)
.findClass("ResolutionTest", GlobalSearchScope.projectScope(project));
if (cls != null) {
PsiMethod[] methods = cls.findMethodsByName("test", false);
if (methods.length == 1) {
PsiMethod method = methods[0];
for (PsiStatement s : method.getBody().getStatements()) {
System.out.println(s.getNode().getText());
process(s);
}
}
}
}

private void process(PsiMethodCallExpression expression) {
PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiMethodCallExpression) {
process((PsiMethodCallExpression) qualifierExpression);
} else if (qualifierExpression instanceof PsiReference) {
System.out.println("Resolving reference " + qualifierExpression.getText());
PsiElement targetElement = ((PsiReference) qualifierExpression).resolve();
if (targetElement == null) {
System.out.println("Resolution failed");
} else if (targetElement instanceof PsiClass) {
System.out.println("Class " + ((PsiClass) targetElement).getName());
} else if (targetElement instanceof PsiVariable) {
System.out.println("Variable " + ((PsiVariable) targetElement).getTypeElement().getText());
}
}

System.out.println("Resolving method " + expression.getMethodExpression().getText());
PsiMethod method = expression.resolveMethod();
if (method == null) {
System.out.println("Resolution failed");
} else {
PsiClass clazz = method.getContainingClass();
System.out.println(clazz.getName() + "." + method.getName());
}
}

private void process(PsiExpression e) {
if (e instanceof PsiMethodCallExpression) {
process((PsiMethodCallExpression) e);
}
}

private void process(PsiStatement s) {
if (s instanceof PsiExpressionStatement) {
process(((PsiExpressionStatement) s).getExpression());
}
}
}

public static void main(String[] args) {
try {
Analyzer analyzer = new Analyzer();
analyzer.add(args);
analyzer.run();
} catch (Exception e) {
e.printStackTrace(System.out);
}
}
}


And output:

verify(mock).simpleMethod();
Resolving method verify
ResolutionTest.verify
Resolving method verify(mock).simpleMethod
Resolution failed

Answer

In order to make this sample to work I had to add rt.jar via projectEnvironment.addJarToClassPath(file); - unfortunately I'm still getting 2 method resolution failures in mockito and I'm unable to create small sample which reproduces an issue. Still information about rt.jar could be useful to someone so I'm adding it as an answer.

Function with issues:

@Test
public void any_should_be_actual_alias_to_anyObject() {
    mock.simpleMethod((Object) null);

    verify(mock).simpleMethod(any());
    verify(mock).simpleMethod(anyObject());
}

My current understanding of an issue: any() return is generic and simpleMethod has multiple overloads and resolver could not pick proper one, but idea itself is able to select proper variant.