Resolving "un-resolvable elements" to fake LightElement

In Mathematica we have over 5000 "keywords"; built-in symbols that are basically unresolvable because they are implemented in a black-box kernel. Colin suggested that it is possible to use a fake LightElement as a target for resolve(), which would be preferable because then I can use findUsages, renaming etc without building much custom code. An even better application is when I have user symbols that I cannot resolve. All symbols with the same name and namespace would be treated as the "same unresolvable" symbol. The main reason is that it is not a good idea for me to return a null reference, because then (even with caching) the expensive resolve code would be called again and again for unresolvable symbols.

Question: Is anyone aware of an example in the community code or a plugin where fake LightElementst are used for that?

Some Details:

I'm using com.intellij.psi.impl.source.resolve.ResolveCache for caching references. The resolver that is called on the first run is the MathematicaSymbolResolver. In this line, you see that I return a LightSymbol for built-in functions. The LightSymbol class is currently a mess since I tried various stuff, but the important thing is that I implemented hashCode() and equals(...) to make two LightSymbols that refer to the same name equal.

Although my references work and built-in function point to the LightElement, I cannot get e.g. "Highlight elements at caret" working which relies on 

com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPass#getUsages(com.intellij.psi.PsiElement, com.intellij.psi.PsiElement, boolean, boolean)

One thing I noticed is that this method calls 

final LocalSearchScope scope = new LocalSearchScope(psiElement);

which is null since for LightElements there is no containingFile given by default. I have fixed that but still com.intellij.psi.search.searches.ReferencesSearch does not find any references, although I can find them when I call resolve() on my built-in symbols in the file. What is the requirement for my LightSymbol so that references from real Symbols are found by IDEA?

0
3 comments

I've replied over in the Gitter chat, but I think the solution to the Highlight elements at caret issue is to implement a HighlightUsagesHandler which returns your LightSymbol from the getTargets() if it's called with your in-editor PsiElement.

A couple of other things I noticed - your LightSymbol implements PsiReference, which I don't think makes sense (other things resolve to it, but it doesn't resolve to anything). I also implement PsiTarget, which allows me to prevent navigation.

0
Avatar
Permanently deleted user

there are a number of methods that should be implemented in order to find usages to work, for example:

- com.intellij.psi.PsiElement#getUseScope should return correct scope from which this element can be referenced

- element should be instanceof PsiNamedElement to be able to get the name to be scanned for

- element.getContainingFile() should return corresponding PsiFile

Please see com.intellij.psi.impl.search.CachesBasedRefSearcher for what's going on during find usages 

0
Avatar
Permanently deleted user

Hey Colin and Alexey,

thanks for your comments. With your help, I was able to extend my understanding and fix the problem. One key point I was implementing wrong is how to ensure that LightElements that represent the same symbol name are treated as equal. For testing purposes I included a HashMap to ensure that all PsiElement variables with the same name point to the same unique LightElement and it worked. That finally tipped me off to look closer when and how my LightElement targets are compared. The key point here was inside PsiManagerImpl

@Override
public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) {
ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly

if (element1 == element2) return true;
if (element1 == null || element2 == null) {
return false;
}

return element1.equals(element2) || element1.isEquivalentTo(element2) || element2.isEquivalentTo(element1);
}

So what I had to do was to implement a reasonable isEquivalentTo(element) for my LightElement class. Here is my final implementation that works

public class LightSymbol extends LightElement implements PsiNamedElement {
private String myName;
private PsiFile myFile;

LightSymbol(@NotNull Symbol symbol) {
super(symbol.getManager(), MathematicaLanguage.INSTANCE);
myName = symbol.getText();
myFile = symbol.getContainingFile();
}

@Override
public String toString() {
return myName;
}

@Override
public String getName() {
return myName;
}

@Override
public PsiFile getContainingFile() {
return myFile;
}

@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
myName = name;
return this;
}

@NotNull
@Override
public SearchScope getUseScope() {
return myFile.getUseScope();
}

@Override
public boolean isEquivalentTo(PsiElement another) {
if (another instanceof Symbol || another instanceof LightSymbol) {
final String name = ((PsiNamedElement) another).getName();
return myName.equals(name);
}
return false;
}
}

Thanks for your help

Cheers
Patrick

0

Please sign in to leave a comment.