FindReferences in RenamePsiElementProcessor

Answered

I'm facing a weird situation, where not all of my referenced variables are renamed. I have a working ReferenceContributor for both, references of variables and references of variable names inside strings. Here is a short code, where the arrows indicate, that all variable usages are resolved to the first "foo"

As you can see all variable references are correctly highlighted by "Highlight element under caret" and indeed, when I when I start to rename, it seems everything works

Unfortunately, the moment I press Enter, the last "newName" is not renamed. I could pin the problem down to

com.intellij.refactoring.rename.RenamePsiElementProcessor#findReferences(com.intellij.psi.PsiElement)

which calls

ReferencesSearch.search(element, GlobalSearchScope.projectScope(element.getProject())).findAll();

I have no idea, why this search doesn't find all references when Show Usage and other features work fine.

Why is that and how can I fix it?

13 comments
Comment actions Permalink
Official comment

The usage search first gets all occurrences of "foo" in project, then finds references at those offsets and checks if they point to the correct target. By default, "var_foo" is treated as one word and so "foo" inside it isn't found. I don't know why it is found in some cases, maybe there's some other code in in-place rename. But the general reason seems to be that "foo" isn't a separate word.

There are two ways of dealing with it:

1. implement your own WordsScanner and process foo in var_foo as a word

2. (probably better) create a referencesSearcher that searches for var_foo words in your file type scope. You can look at com.intellij.psi.impl.search.SimpleAccessorReferenceSearcher as an example.

Comment actions Permalink

Thank you for your answer, Peter. One question, in Mathematica the underscore is an operator. Therefore, you can look at var_foo as something like

var-foo

in Java. So if the reference searcher, uses my Psi tree, it never sees a variable foo_bar. It always sees two separate variables connected with an operation. Why would the referenceSearcher rather search the text of my file instead of inspecting the Psi tree? This is unexpected.

0
Comment actions Permalink

I didn't realize it's an operator, this changes everything, of course. So your lexer processes var_foo as two different words, right? Then CachesBasedRefSearcher should search the reference correctly.

Have you implemented PsiReference#handleElementRename? Is it called at all during the rename? Is PsiReference#isReferenceTo called?

0
Comment actions Permalink

Exactly! And when I see this correctly, then CachesBasedRefSearcher does find everything, because it is used in the IdentifierHighlighterPass that marks all occurrences when I'm over an identifier with the cursor. Let me give a very short example showing the Psi tree. A "Symbol" is an identifier and you find 4 Symbols in the following code: "foo = 1" is "Set Symbol to Number", "f[var_foo]" is "FunctionCall (function Symbol f) of Symbol var with the Blank operator and Symbol foo"

The moment I press Shift+F6, CachesBasedRefSearcher is called again and it finds again all references because now I rename everything in the preview

The moment I press Enter, however, there is only 1 usage found in

com.intellij.refactoring.rename.RenameProcessor#findUsages

and I have absolutely no idea why. Do you have any hints how I can debug this further?

Edit: And in this particular case, the handleElementRename is only called for the first foo. However, renaming in other very complex constructs with local variables etc. does work correctly and from the viewpoint of the Mathematica language, the "_" operator is similar to all other operators like +,*, etc. 

 

0
Comment actions Permalink

Is PsiReference#isReferenceTo called when finishing in-place rename?

0
Comment actions Permalink

Yes, but only one time for the "self-reference". Since "foo = 1" is the place of definition for foo, it gets a reference that resolves to itself. Here is a excerpt of the thread-dump

at de.halirutan.mathematica.parsing.psi.impl.SymbolPsiReference.isReferenceTo(SymbolPsiReference.java:130)
at com.intellij.psi.search.SingleTargetRequestResultProcessor.processTextOccurrence(SingleTargetRequestResultProcessor.java:52)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$8.lambda$execute$0(PsiSearchHelperImpl.java:734)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$8$$Lambda$748.1863423400.execute(Unknown Source:-1)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processTreeUp(LowLevelSearchUtil.java:138)
at com.intellij.psi.impl.search.LowLevelSearchUtil.processElementsAtOffsets(LowLevelSearchUtil.java:224)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$8.execute(PsiSearchHelperImpl.java:730)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:232)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$2.processInReadAction(PsiSearchHelperImpl.java:228)
at com.intellij.openapi.application.ReadActionProcessor$1.compute(ReadActionProcessor.java:32)
at com.intellij.openapi.application.ReadActionProcessor$1.compute(ReadActionProcessor.java:29)
at com.intellij.openapi.application.impl.ApplicationImpl.runReadAction(ApplicationImpl.java:895)
at com.intellij.openapi.application.ReadActionProcessor.process(ReadActionProcessor.java:29)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processVirtualFile$4(PsiSearchHelperImpl.java:376)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$$Lambda$912.1197524689.run(Unknown Source:-1)
at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1055)
at com.intellij.openapi.application.ex.ApplicationUtil.tryRunReadAction(ApplicationUtil.java:46)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processVirtualFile(PsiSearchHelperImpl.java:360)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.lambda$processPsiFileRoots$2(PsiSearchHelperImpl.java:313)
at com.intellij.psi.impl.search.PsiSearchHelperImpl$$Lambda$910.509346699.process(Unknown Source:-1)
at com.intellij.concurrency.JobLauncherImpl.lambda$null$0(JobLauncherImpl.java:100)
at com.intellij.concurrency.JobLauncherImpl$$Lambda$747.1264193158.run(Unknown Source:-1)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:568)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:519)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:54)
at com.intellij.concurrency.JobLauncherImpl.lambda$processImmediatelyIfTooFew$1(JobLauncherImpl.java:96)
at com.intellij.concurrency.JobLauncherImpl$$Lambda$746.116216177.run(Unknown Source:-1)
at com.intellij.concurrency.JobLauncherImpl.processImmediatelyIfTooFew(JobLauncherImpl.java:110)
at com.intellij.concurrency.JobLauncherImpl.invokeConcurrentlyUnderProgress(JobLauncherImpl.java:53)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processPsiFileRoots(PsiSearchHelperImpl.java:325)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processElementsWithTextInGlobalScope(PsiSearchHelperImpl.java:281)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.bulkProcessElementsWithWord(PsiSearchHelperImpl.java:179)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processSingleRequest(PsiSearchHelperImpl.java:897)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processGlobalRequestsOptimized(PsiSearchHelperImpl.java:638)
at com.intellij.psi.impl.search.PsiSearchHelperImpl.processRequests(PsiSearchHelperImpl.java:586)
at com.intellij.psi.search.SearchRequestQuery.processResults(SearchRequestQuery.java:45)
at com.intellij.util.AbstractQuery.forEach(AbstractQuery.java:79)
at com.intellij.util.MergeQuery.processSubQuery(MergeQuery.java:85)
at com.intellij.util.MergeQuery.forEach(MergeQuery.java:57)
at com.intellij.util.UniqueResultsQuery.process(UniqueResultsQuery.java:66)
at com.intellij.util.UniqueResultsQuery.forEach(UniqueResultsQuery.java:56)
at com.intellij.util.UniqueResultsQuery.findAll(UniqueResultsQuery.java:79)
at com.intellij.refactoring.rename.RenamePsiElementProcessor.findReferences(RenamePsiElementProcessor.java:70)
at com.intellij.refactoring.rename.RenamePsiElementProcessor.findReferences(RenamePsiElementProcessor.java:65)
at com.intellij.refactoring.rename.RenameUtil.findUsages(RenameUtil.java:72)
at com.intellij.refactoring.rename.RenameProcessor.findUsages(RenameProcessor.java:286)
at com.intellij.refactoring.BaseRefactoringProcessor$1$1.compute(BaseRefactoringProcessor.java:168)
at com.intellij.refactoring.BaseRefactoringProcessor$1$1.compute(BaseRefactoringProcessor.java:165)

 

0
Comment actions Permalink

When that breakpoint is hit, could you please go up the stack to LowLevelSearchUtil.processElementsAtOffsets and check searcher.myJavaIdentifier field? Is it true?

0
Comment actions Permalink

Yes, this field is true.

0
Comment actions Permalink

I see. It appears to be a bug in the searching infrastructure. Sorry for that. I've filed https://youtrack.jetbrains.com/issue/IDEA-165760, please vote/watch it.

As a workaround, you can add your own referencesSearcher that invokes com.intellij.psi.impl.search.PsiSearchHelperImpl#processElementsWithWord(com.intellij.psi.search.TextOccurenceProcessor, com.intellij.psi.search.SearchScope, java.lang.String, short, java.util.EnumSet<com.intellij.psi.impl.search.PsiSearchHelperImpl.Options>, java.lang.String) and doesn't pass PROCESS_ONLY_JAVA_IDENTIFIERS_IF_POSSIBLE option.

0
Comment actions Permalink

I really do appreciate all your help!

Additionally, I have the same problems with variables that contain dollar signs like $var. This is even weirder because they are normal identifiers. So in a code like this

$var = 1;
$var + $var

only the definition element at "$var =  1" is renamed, no matter from which position I start the renaming.

What brings the confusion to an even higher level is the setting of these two methods

com.intellij.lang.refactoring.RefactoringSupportProvider#isMemberInplaceRenameAvailable
com.intellij.lang.refactoring.RefactoringSupportProvider#isInplaceRenameAvailable

as soon as I return false and Idea present dialog for renaming the underscore variables are handled correctly. The dollar sign variables however still don't work.

Since there is no documentation on these two methods, can you comment on what the difference between isMemberInplaceRenameAvailable and isInplaceRenameAvailable is?

 

0
Comment actions Permalink

The both inplace rename options do more or less the same, in one case MemberInplaceRenamer would perform the work in other case - VariableInplaceRenamer. The difference between them is caused by the scope where declaration could apper, if it's local, then it can be VariableInplaceRenamer otherwise, Member. For members case, first the usages in one file are searched and on exit of the template, everything would be reverted and the refactoring would actually performed from scratch on unchanged sources. In variable case, no revert should be performed and on exit of the template just some local conflicts could be resolved. If you return true from both methods, then VariableHandler should win (as declared first).

How do you start the inplace refactorings then? Do you see the suggestion to rename comments?

Thanks

0
Comment actions Permalink

Merry Christmas Anna,

now I understand the weird behaviour. I looked up your code which contains a separate method the "pre-collects" all reference to show the red frame and the rename preview:

com.intellij.refactoring.rename.inplace.MemberInplaceRenamer#collectRefs

But no matter what references you found, you call the usual RenameProcessor with my starting element and this processor does not find all the Usages of my references due to the optimisations Peter was speaking about.

> How do you start the inplace refactorings then? Do you see the suggestion to rename comments?

Always with Shift+F6, but I tried different settings of isInplaceRenameAvailable and isMemberInplaceRenameAvailable just to see if it makes a difference. I see now, why it makes no difference since they all rely on

com.intellij.refactoring.rename.RenameProcessor#findUsages

I'm probably going to ask another questions about this, but thank you very much for all the information.

0
Comment actions Permalink

@Peter For me, an easier fix is to implement a small RenamePsiElementProcessor that just combines the results that I find and the one that idea finds. I haven't tested it thoroughly but is seems to do the trick:

public class MathematicaPsiRenameProcessor extends RenamePsiElementProcessor {
@Override
public boolean canProcessElement(@NotNull PsiElement element) {
return element instanceof Symbol || element instanceof MString;
}

@NotNull
@Override
public Collection<PsiReference> findReferences(PsiElement element) {
final Collection<PsiReference> references = super.findReferences(element);
final PsiReference[] myReferences = element.getReferences();
for (PsiReference ref : myReferences) {
if (!references.contains(ref)) {
references.add(ref);
}
}
return references;
}
}

 

0

Please sign in to leave a comment.