UserDataHolder - file invalidated by unknown operation, making PsiType invalid
I'm working on a plugin that lets users launch IntelliJ from a web browser. The browser sends over a bunch of information, including the expected return type of a script file that the user will work on in IntelliJ. Among other things, I want to provide an Annotator that checks the script's return statements to make sure that they are returning the correct type.
I've been putting the expected return type in a UserDataHolder (the PsiFile's holder, specifically), then retrieving that data from the same holder in my custom annotator.
The trouble is, the underlying PsiFile seems to get destroyed/invalidated at some point, so the PsiFile instance that the annotator has access to doesn't have the needed user data.
I've tried to work around this by storing the data on the PsiFile's corresponding VirtualFile, which, I'm led to believe, won't be destroyed unexpectedly.
The problem is, the data that I'm storing is a PsiType, which itself becomes invalid. Because of that, any calls to the isAssignableFrom() method will throw an exception.
I'm guessing that the same operation that invalidates the PsiFile is also invalidating the PsiType object. This may be because I created the PsiType object using a PsiElementFactory created from the PsiFile object. In brief,
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(psiFile.getProject()).getElementFactory();
PsiClassType type = elementFactory.createTypeByFQClassName(baseClass);
Log entries like this make me suspect that IntelliJ's doing some work under the hood that recreates the PsiFile.
INFO - penapi.project.DumbServiceImpl - cancel com.intellij.util.indexing.UnindexedFilesUpdater@796d0505
I'm not sure how best to workaround the problem. Should I try to create a new instance of the PsiType when my annotator runs if it's invalid? Should I simply store the return type's fully qualified name as a string, then re-instantiate the PsiType in my annotator? Should I try to find some way to make sure my PsiFile doesn't get invalidated/destroyed?
tl;dr: How can I store a return type in some kind of UserDataHolder (or similar structure) without it going invalid on me?
请先登录再写评论。
Physical PSI is only guaranteed to survive inside a single read action. Anything can happen between those, including total invalidation of everything. So you shouldn't store PSI or PsiType in user data. For better survival, you can wrap PSI into SmartPsiElementPointer and PsiType into SmartTypePointer (by calling SmartTypePointerManager). You might also consider storing type's text instead of type.
And yes, you shouldn't expect file's user data to survive between consecutive read actions. Storing user data in VirtualFile is the simplest way around that, another possibility being storing the data in some project component/service. VFS user data is application-wide, so if you're not careful, you can easily leak a project (via PSI or PsiType) via VirtualFile user data, after that project has been already closed. Project components don't have this issue.
Excellent! Thanks much, Peter. I did consider storing the type as a String, but the SmartTypePointer coupled with storing on the VirtualFile object worked a treat.
As a side note, I'd pulled off an ugly workaround by doing this in my annotator. This is Groovy code, if you're curious how I'm bypassing the getters.
if (!expectedPsiReturnType.isValid()) {def fqdn = expectedPsiReturnType.myReference.myValue.myCanonicalText.toString()
expectedPsiReturnType = PsiType.getTypeByName(fqdn,
containingFile.project,
containingFile.resolveScope)
}
Where containingFile is the PsiElement's getOriginalFile() value, and expectedPsiReturnType is the PsiClassType I pulled from the UserDataHolder.
That works, but SmartTypePointer is way more elegant and appears to solve the same problems.
I'm not too concerned about leaking the project in this particular context, though if I should be more worried, I'm open to instruction. Cheers!