mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
simplify stub-AST-PSI relationships
replace AstPath with index-based SpineRef, take coarser locks, load multi-root stubs independently What we lose: * an ability to gc AST of stubbed PSI loaded via AST-only * an ability to gc AST of non-stubbed StubBasedPsiElementBase * partial chameleon expansion during findElementAt after AST has been gc-ed but stubbed PSI is still referenced ** probably not a major regression: full parsing has been happening during stub-AST switch for ages What we retain: * we can hold references to PSI received from stub index queries without worrying about retaining their AST (even if loaded, it will be gc-ed) What we gain: * simpler code with less mutable state * outdated AstPath assertion is now nonexistent (WEB-32120, EA-101911) * no more cases when stub has been gc-ed but can't be reloaded due to some cached PSI with unknown stub index (IDEA-189948) * other stub-related exceptions are also likely to be fixed: ** EA-97101 - REWA: StubBasedPsiElementBase.failedToBindStubToAst ** EA-102022 - CCE: PsiFieldImpl.findFirstFieldInDeclaration ** EA-101452 - assert: StubBasedPsiElementBase.getRequiredStubOrPsiChild ** EA-107208 - ISE: StubBasedPsiElementBase.$$$reportNull$$$ ** EA-110161 - PIEAE: SubstrateRef$StubRef.reportError ** EA-113453 - CCE: PsiClassImpl.getContainingClass ** EA-117888 - CCE: PsiClassImpl.getContextStub
This commit is contained in:
@@ -40,7 +40,6 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.Processor;
|
||||
import java.util.HashSet;
|
||||
import com.intellij.util.containers.MostlySingularMultiMap;
|
||||
import com.intellij.util.indexing.IndexingDataKeys;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -48,6 +47,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJavaFile {
|
||||
@@ -137,8 +137,8 @@ public abstract class PsiJavaFileBaseImpl extends PsiFileImpl implements PsiJava
|
||||
if (stub != null) {
|
||||
PsiImportList[] nodes = stub.getChildrenByType(JavaStubElementTypes.IMPORT_LIST, PsiImportList.ARRAY_FACTORY);
|
||||
if (nodes.length == 1) return nodes[0];
|
||||
if (nodes.length == 0) return null;
|
||||
reportStubAstMismatch(stub + "; " + stub.getChildrenStubs(), getStubTree());
|
||||
assert nodes.length == 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
ASTNode node = calcTreeElement().findChildByType(JavaElementType.IMPORT_LIST);
|
||||
|
||||
@@ -34,15 +34,13 @@ import com.intellij.psi.stubs.StubTree
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.reference.SoftReference
|
||||
import com.intellij.testFramework.LeakHunter
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.testFramework.SkipSlowTestLocally
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
import com.intellij.util.ref.GCUtil
|
||||
import groovy.transform.CompileStatic
|
||||
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.Future
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
@@ -236,7 +234,7 @@ class B {
|
||||
LeakHunter.checkLeak(file, StubTree) { candidate -> candidate.root.psi == file }
|
||||
}
|
||||
|
||||
void "test node is not deeply parsed when loaded in green stub presence"() {
|
||||
void "test node has same PSI when loaded in green stub presence"() {
|
||||
PsiFileImpl file = (PsiFileImpl)myFixture.addFileToProject("a.java", "class A<T>{}")
|
||||
def stubTree = file.stubTree
|
||||
PsiClass psiClass = ((PsiJavaFile)file).classes[0]
|
||||
@@ -244,7 +242,7 @@ class B {
|
||||
GCUtil.tryGcSoftlyReachableObjects()
|
||||
|
||||
assert stubTree.is(file.greenStubTree)
|
||||
assert !file.node.parsed
|
||||
assert file.node.lastChildNode.psi.is(psiClass)
|
||||
}
|
||||
|
||||
void "test load stub from non-file PSI after AST is unloaded"() {
|
||||
@@ -258,26 +256,22 @@ class B {
|
||||
assert ((PsiClassImpl) cls).stub
|
||||
}
|
||||
|
||||
void "test load PSI via stub when AST is gc-ed but PSI exists that has never known stub"() {
|
||||
void "test load PSI via stub when AST is gc-ed but PSI exists that was loaded via AST but knows its stub index"() {
|
||||
PsiJavaFileImpl file = (PsiJavaFileImpl)myFixture.addFileToProject("a.java", "class A{}")
|
||||
def cls = file.lastChild
|
||||
assert cls instanceof PsiClass
|
||||
|
||||
GCUtil.tryGcSoftlyReachableObjects()
|
||||
assert !file.treeElement
|
||||
|
||||
assert cls == myFixture.findClass('A')
|
||||
}
|
||||
|
||||
void "test load PSI via stub when AST is gc-ed and PSI remains that never knew stub"() {
|
||||
PsiJavaFileImpl file = (PsiJavaFileImpl)myFixture.addFileToProject("a.java", "class A{}")
|
||||
def cls = file.lastChild
|
||||
assert cls instanceof PsiClass
|
||||
assert file.treeElement // we still hold a strong reference to AST
|
||||
|
||||
assert cls == myFixture.findClass('A')
|
||||
|
||||
// now we know stub index and can GC AST
|
||||
GCUtil.tryGcSoftlyReachableObjects()
|
||||
assert !file.treeElement
|
||||
|
||||
assert cls == myFixture.findClass('A')
|
||||
assert !file.treeElement
|
||||
}
|
||||
|
||||
void "test bind stubs to AST after AST has been loaded and gc-ed"() {
|
||||
@@ -335,30 +329,27 @@ class B {
|
||||
assert stubTree.plainList.find { it.stubType == JavaStubElementTypes.ANONYMOUS_CLASS }
|
||||
}
|
||||
|
||||
@CompileStatic
|
||||
void "test getStub performance with cached PSI"() {
|
||||
def text = "class Foo { " + "void bar(int a, int b, int c, int d, int e) { int x = null; }\n" * 1000 + "}"
|
||||
def file = myFixture.addFileToProject "a.java", text
|
||||
|
||||
PsiMethod[] methods = ((PsiJavaFile) file).classes[0].methods
|
||||
def params = methods.collect { PsiMethod method -> method.parameterList.parameters }
|
||||
def literal = file.findElementAt(text.indexOf('null')).parent as PsiLiteralExpression // the only cached PSI without stubIndex
|
||||
void "test stub index is cleared on AST change"() {
|
||||
def clazz = myFixture.addClass("class Foo { int a; }")
|
||||
def field = clazz.fields[0]
|
||||
def file = clazz.containingFile as PsiFileImpl
|
||||
WriteCommandAction.runWriteCommandAction(project, {
|
||||
file.viewProvider.document.insertString(0, ' ')
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
})
|
||||
|
||||
assert file.calcStubTree()
|
||||
|
||||
WriteCommandAction.runWriteCommandAction(project, {
|
||||
file.viewProvider.document.insertString(file.text.indexOf('int'), 'void foo();')
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
})
|
||||
|
||||
GCUtil.tryGcSoftlyReachableObjects()
|
||||
|
||||
def fileImpl = (PsiFileImpl)file
|
||||
assert !fileImpl.treeElement
|
||||
assert !fileImpl.stub
|
||||
assert file.calcStubTree()
|
||||
|
||||
PlatformTestUtil.startPerformanceTest('getStub performance', 100, {
|
||||
10_000.times {
|
||||
if (fileImpl.stub != null) {
|
||||
throw new IllegalStateException("has stub")
|
||||
}
|
||||
}
|
||||
}).assertTiming()
|
||||
|
||||
assert params
|
||||
assert literal
|
||||
assert field.valid
|
||||
assert field.name == 'a'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ public abstract class StubBase<T extends PsiElement> extends ObjectStubBase<Stub
|
||||
return myStubList.findChildStubByType(id, elementType);
|
||||
}
|
||||
|
||||
public void setPsi(@NotNull final T psi) {
|
||||
public void setPsi(@NotNull T psi) {
|
||||
assert myPsi == null || myPsi == psi;
|
||||
myPsi = psi;
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,6 @@ public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegateP
|
||||
public static final Key<String> CREATION_TRACE = Key.create("CREATION_TRACE");
|
||||
public static final boolean ourTraceStubAstBinding = "true".equals(System.getProperty("trace.stub.ast.binding", "false"));
|
||||
private volatile SubstrateRef mySubstrateRef;
|
||||
private volatile int myStubIndex = -1;
|
||||
private final IElementType myElementType;
|
||||
|
||||
public StubBasedPsiElementBase(@NotNull T stub, @NotNull IStubElementType nodeType) {
|
||||
@@ -238,29 +237,6 @@ public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegateP
|
||||
*/
|
||||
public final void setSubstrateRef(@NotNull SubstrateRef substrateRef) {
|
||||
mySubstrateRef = substrateRef;
|
||||
myStubIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't invoke this method, it's public for implementation reasons.
|
||||
*/
|
||||
public final void setStubIndex(int stubIndex) {
|
||||
myStubIndex = stubIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't invoke this method, it's public for implementation reasons.
|
||||
*/
|
||||
public int getStubIndex() {
|
||||
return myStubIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't invoke this method, it's public for implementation reasons.
|
||||
*/
|
||||
@NotNull
|
||||
public final SubstrateRef getSubstrateRef() {
|
||||
return mySubstrateRef;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -383,7 +359,7 @@ public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegateP
|
||||
public T getStub() {
|
||||
ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often
|
||||
//noinspection unchecked
|
||||
return (T)mySubstrateRef.getStub(myStubIndex);
|
||||
return (T)mySubstrateRef.getStub();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,7 +371,7 @@ public class StubBasedPsiElementBase<T extends StubElement> extends ASTDelegateP
|
||||
public final T getGreenStub() {
|
||||
ProgressIndicatorProvider.checkCanceled(); // Hope, this is called often
|
||||
//noinspection unchecked
|
||||
return (T)mySubstrateRef.getGreenStub(myStubIndex);
|
||||
return (T)mySubstrateRef.getGreenStub();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.psi.impl.source;
|
||||
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.LowMemoryWatcher;
|
||||
import com.intellij.openapi.util.UserDataHolderEx;
|
||||
import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.psi.impl.source.tree.AstPath;
|
||||
import com.intellij.psi.impl.source.tree.CompositeElement;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A weak cache for all instantiated stub-based PSI to allow {@link CompositeElement#getPsi()} return it when AST is reloaded.<p/>
|
||||
*
|
||||
* All methods should be called under an external lock (view provider's PsiLock), except for
|
||||
* ({@link #getCachedPsi(AstPath)} which can be called without lock.
|
||||
*
|
||||
* @author peter
|
||||
*/
|
||||
class AstPathPsiMap {
|
||||
/**
|
||||
* Not using ConcurrentWeakValueMap because we need to clean each of them up separately, when ASTs in thousands of files are created and gc-ed.
|
||||
* So we have a per-project single shared reference queue {@link #myQueue} for that.
|
||||
* Otherwise the files end up retaining lots of maps with all-gc-ed stuff inside, but the maps are still very large.
|
||||
*/
|
||||
private final ConcurrentMap<AstPath, MyReference> myMap = ContainerUtil.newConcurrentMap();
|
||||
private volatile Boolean myHasUnbindableCachedPsi = null;
|
||||
|
||||
private static final Key<MyReferenceQueue> STUB_PSI_REFS = Key.create("STUB_PSI_REFS");
|
||||
private final MyReferenceQueue myQueue;
|
||||
|
||||
AstPathPsiMap(@NotNull Project project) {
|
||||
MyReferenceQueue queue = project.getUserData(STUB_PSI_REFS);
|
||||
myQueue = queue != null ? queue : ((UserDataHolderEx)project).putUserDataIfAbsent(STUB_PSI_REFS, new MyReferenceQueue(project));
|
||||
}
|
||||
|
||||
void invalidatePsi() {
|
||||
myQueue.cleanupStaleReferences();
|
||||
getAllCachedPsi().forEach(psi -> {
|
||||
DebugUtil.onInvalidated(psi);
|
||||
psi.setSubstrateRef(SubstrateRef.createInvalidRef(psi));
|
||||
});
|
||||
myMap.clear();
|
||||
myHasUnbindableCachedPsi = false;
|
||||
}
|
||||
|
||||
void switchToStrongRefs() {
|
||||
myQueue.cleanupStaleReferences();
|
||||
getAllCachedPsi().forEach(psi -> {
|
||||
CompositeElement node = (CompositeElement)psi.getNode();
|
||||
node.setPsi(psi);
|
||||
psi.setSubstrateRef(SubstrateRef.createAstStrongRef(node));
|
||||
});
|
||||
myMap.clear();
|
||||
myHasUnbindableCachedPsi = false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
StubBasedPsiElementBase<?> getCachedPsi(@NotNull AstPath ref) {
|
||||
return SoftReference.dereference(myMap.get(ref));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
StubBasedPsiElementBase<?> cachePsi(@NotNull AstPath key, @NotNull StubBasedPsiElementBase psi) {
|
||||
myQueue.cleanupStaleReferences();
|
||||
// ensure PSI will use AST path before making it available to other threads
|
||||
// otherwise another thread could invoke StubRef.getNode and fail since file's AST isn't set yet
|
||||
psi.setSubstrateRef(key);
|
||||
myMap.put(key, new MyReference(psi, key, myQueue));
|
||||
clearStubIndexCache();
|
||||
return psi;
|
||||
}
|
||||
|
||||
Stream<? extends StubBasedPsiElementBase<?>> getAllCachedPsi() {
|
||||
myQueue.cleanupStaleReferences();
|
||||
if (myMap.isEmpty()) return Stream.empty();
|
||||
|
||||
return myMap.values().stream().map(Reference::get).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
boolean hasUnbindableCachedPsi() {
|
||||
Boolean answer = myHasUnbindableCachedPsi;
|
||||
if (answer == null) {
|
||||
myHasUnbindableCachedPsi = answer = calcHasUnbindableCachedPsi();
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
private boolean calcHasUnbindableCachedPsi() {
|
||||
myQueue.cleanupStaleReferences();
|
||||
if (myMap.isEmpty()) return false;
|
||||
|
||||
return getAllCachedPsi().anyMatch(e -> e.getStubIndex() < 0);
|
||||
}
|
||||
|
||||
void clearStubIndexCache() {
|
||||
myHasUnbindableCachedPsi = null;
|
||||
}
|
||||
|
||||
private static class MyReference extends WeakReference<StubBasedPsiElementBase<?>> {
|
||||
final AstPath pathRef;
|
||||
|
||||
MyReference(StubBasedPsiElementBase psi, AstPath ref, ReferenceQueue<StubBasedPsiElementBase<?>> queue) {
|
||||
super(psi, queue);
|
||||
pathRef = ref;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MyReferenceQueue extends ReferenceQueue<StubBasedPsiElementBase<?>> {
|
||||
MyReferenceQueue(Project project) {
|
||||
LowMemoryWatcher.register(() -> cleanupStaleReferences(), project);
|
||||
}
|
||||
|
||||
void cleanupStaleReferences() {
|
||||
while (true) {
|
||||
MyReference reference = (MyReference)poll();
|
||||
if (reference == null) break;
|
||||
|
||||
AstPath key = reference.pathRef;
|
||||
AstPathPsiMap refToPsi = key.getContainingFile().getRefToPsi();
|
||||
refToPsi.myMap.remove(key, reference);
|
||||
refToPsi.clearStubIndexCache();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,32 +15,48 @@
|
||||
*/
|
||||
package com.intellij.psi.impl.source;
|
||||
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Getter;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.psi.impl.source.tree.CompositeElement;
|
||||
import com.intellij.psi.impl.source.tree.FileElement;
|
||||
import com.intellij.psi.stubs.PsiFileStubImpl;
|
||||
import com.intellij.psi.stubs.StubTree;
|
||||
import com.intellij.psi.stubs.*;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.util.Set;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
final class FileTrees {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.FileTrees");
|
||||
private static final int firstNonFilePsiIndex = 1;
|
||||
private final PsiFileImpl myFile;
|
||||
private final Reference<StubTree> myStub;
|
||||
private final Getter<FileElement> myTreeElementPointer; // SoftReference/WeakReference to ASTNode or a strong reference to a tree if the file is a DummyHolder
|
||||
final boolean astLoaded;
|
||||
final boolean useStrongRefs;
|
||||
|
||||
/** Keeps references to all alive stubbed PSI (using {@link SpineRef}) to ensure PSI identity is preserved after AST/stubs are gc-ed and reloaded */
|
||||
@Nullable private final List<Reference<StubBasedPsiElementBase>> myRefToPsi;
|
||||
|
||||
private FileTrees(@Nullable Reference<StubTree> stub, @Nullable Getter<FileElement> ast, boolean astLoaded, boolean useStrongRefs) {
|
||||
private FileTrees(@NotNull PsiFileImpl file,
|
||||
@Nullable Reference<StubTree> stub,
|
||||
@Nullable Getter<FileElement> ast,
|
||||
@Nullable List<Reference<StubBasedPsiElementBase>> refToPsi) {
|
||||
this.myFile = file;
|
||||
this.myStub = stub;
|
||||
this.myTreeElementPointer = ast;
|
||||
this.astLoaded = astLoaded;
|
||||
this.useStrongRefs = useStrongRefs;
|
||||
this.myRefToPsi = refToPsi;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -54,7 +70,41 @@ final class FileTrees {
|
||||
}
|
||||
|
||||
FileTrees switchToStrongRefs() {
|
||||
return new FileTrees(myStub, myTreeElementPointer, astLoaded, true);
|
||||
if (myRefToPsi == null) return this;
|
||||
|
||||
getAllCachedPsi(myRefToPsi).forEach(psi -> {
|
||||
ASTNode node = psi.getNode();
|
||||
LOG.assertTrue(node.getPsi() == psi);
|
||||
psi.setSubstrateRef(SubstrateRef.createAstStrongRef(node));
|
||||
});
|
||||
|
||||
return new FileTrees(myFile, myStub, myTreeElementPointer, null);
|
||||
}
|
||||
|
||||
private static Stream<StubBasedPsiElementBase> getAllCachedPsi(@NotNull List<Reference<StubBasedPsiElementBase>> refToPsi) {
|
||||
return refToPsi.stream().map(SoftReference::dereference).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
boolean useSpineRefs() {
|
||||
return myRefToPsi != null;
|
||||
}
|
||||
|
||||
FileTrees switchToSpineRefs(@NotNull StubbedSpine srcSpine) {
|
||||
List<Reference<StubBasedPsiElementBase>> refToPsi = myRefToPsi;
|
||||
if (refToPsi == null) refToPsi = new ArrayList<>(Collections.nCopies(srcSpine.getStubCount(), null));
|
||||
|
||||
for (int i = firstNonFilePsiIndex; i < refToPsi.size(); i++) {
|
||||
StubBasedPsiElementBase psi = (StubBasedPsiElementBase)srcSpine.getStubPsi(i);
|
||||
assert psi != null;
|
||||
psi.setSubstrateRef(new SpineRef(myFile, i));
|
||||
StubBasedPsiElementBase existing = SoftReference.dereference(refToPsi.get(i));
|
||||
if (existing != null) {
|
||||
assert existing == psi : "Duplicate PSI found";
|
||||
} else {
|
||||
refToPsi.set(i, new WeakReference<>(psi));
|
||||
}
|
||||
}
|
||||
return new FileTrees(myFile, myStub, myTreeElementPointer, refToPsi);
|
||||
}
|
||||
|
||||
FileTrees clearStub(@NotNull String reason) {
|
||||
@@ -62,29 +112,104 @@ final class FileTrees {
|
||||
if (stubHolder != null) {
|
||||
((PsiFileStubImpl<?>)stubHolder.getRoot()).clearPsi(reason);
|
||||
}
|
||||
return new FileTrees(null, myTreeElementPointer, astLoaded, useStrongRefs);
|
||||
|
||||
if (myRefToPsi != null) {
|
||||
DebugUtil.performPsiModification("clearStub", () -> getAllCachedPsi(myRefToPsi).forEach(psi -> {
|
||||
DebugUtil.onInvalidated(psi);
|
||||
psi.setSubstrateRef(SubstrateRef.createInvalidRef(psi));
|
||||
}));
|
||||
}
|
||||
|
||||
return new FileTrees(myFile, null, myTreeElementPointer, null);
|
||||
}
|
||||
|
||||
FileTrees withAst(@NotNull Getter<FileElement> ast) {
|
||||
return new FileTrees(myStub, ast, true, useStrongRefs);
|
||||
return new FileTrees(myFile, myStub, ast, myRefToPsi).reconcilePsi(derefStub(), ast.get(), true);
|
||||
}
|
||||
|
||||
FileTrees withExclusiveStub(@NotNull StubTree stub, Set<PsiFileImpl> allRoots) {
|
||||
if (derefTreeElement() != null || useStrongRefs) {
|
||||
throw new RuntimeException(toString() + "; roots=" + allRoots + "; root trees=" + ContainerUtil.map(allRoots, PsiFileImpl::getFileTrees));
|
||||
}
|
||||
return new FileTrees(new SoftReference<>(stub), null, false, false);
|
||||
}
|
||||
|
||||
FileTrees withGreenStub(@NotNull StubTree stub, @NotNull PsiFileImpl file) {
|
||||
if (derefTreeElement() == null || !astLoaded) {
|
||||
throw new RuntimeException("No AST in file " + file + " of " + file.getClass() + "; " + this);
|
||||
}
|
||||
return new FileTrees(new SoftReference<>(stub), myTreeElementPointer, true, useStrongRefs);
|
||||
FileTrees withStub(@NotNull StubTree stub, @Nullable FileElement ast) {
|
||||
assert derefTreeElement() == ast;
|
||||
return new FileTrees(myFile, new SoftReference<>(stub), myTreeElementPointer, myRefToPsi)
|
||||
.reconcilePsi(stub, ast, false);
|
||||
}
|
||||
|
||||
static FileTrees noStub(@Nullable FileElement ast, @NotNull PsiFileImpl file) {
|
||||
return new FileTrees(null, ast, ast != null, file instanceof DummyHolder);
|
||||
return new FileTrees(file, null, ast, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures {@link #myRefToPsi}, stubs and AST all have the same PSI at corresponding indices.
|
||||
* In case several sources already have PSI (e.g. created during AST parsing), overwrites them with the "correct" one,
|
||||
* which is taken from {@link #myRefToPsi} if exists, otherwise from either stubs or AST depending on {@code takePsiFromStubs}.
|
||||
*/
|
||||
private FileTrees reconcilePsi(@Nullable StubTree stubTree, @Nullable FileElement astRoot, boolean takePsiFromStubs) {
|
||||
assert stubTree != null || astRoot != null;
|
||||
|
||||
if ((stubTree == null || astRoot == null) && (myRefToPsi == null || !getAllCachedPsi(myRefToPsi).findFirst().isPresent())) {
|
||||
// there's only one source of PSI, nothing to reconcile
|
||||
return new FileTrees(myFile, myStub, myTreeElementPointer, null);
|
||||
}
|
||||
|
||||
List<StubElement<?>> stubList = stubTree == null ? null : stubTree.getPlainList();
|
||||
List<CompositeElement> nodeList = astRoot == null ? null : astRoot.getStubbedSpine().getSpineNodes();
|
||||
|
||||
try {
|
||||
return DebugUtil.performPsiModification("reconcilePsi", () -> {
|
||||
if (myRefToPsi != null) {
|
||||
assert myRefToPsi.size() == (stubList != null ? stubList.size() : nodeList.size()) : "Cached PSI count doesn't match actual one";
|
||||
bindSubstratesToCachedPsi(stubList, nodeList);
|
||||
}
|
||||
|
||||
if (stubList != null && nodeList != null) {
|
||||
assert stubList.size() == nodeList.size() : "Stub count doesn't match stubbed node length";
|
||||
|
||||
StubbedSpine srcSpine = takePsiFromStubs ? stubTree.getSpine() : astRoot.getStubbedSpine();
|
||||
FileTrees result = switchToSpineRefs(srcSpine);
|
||||
bindStubsWithAst(srcSpine, stubList, nodeList, takePsiFromStubs);
|
||||
return result;
|
||||
}
|
||||
return this;
|
||||
});
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error(e);
|
||||
myFile.onContentReload();
|
||||
myFile.rebuildStub();
|
||||
throw StubTreeLoader.getInstance().stubTreeAndIndexDoNotMatch(e.getMessage(), stubTree, myFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSubstratesToCachedPsi(List<StubElement<?>> stubList, List<CompositeElement> nodeList) {
|
||||
assert myRefToPsi != null;
|
||||
for (int i = firstNonFilePsiIndex; i < myRefToPsi.size(); i++) {
|
||||
StubBasedPsiElementBase cachedPsi = SoftReference.dereference(myRefToPsi.get(i));
|
||||
if (cachedPsi != null) {
|
||||
if (stubList != null) {
|
||||
//noinspection unchecked
|
||||
((StubBase)stubList.get(i)).setPsi(cachedPsi);
|
||||
}
|
||||
if (nodeList != null) {
|
||||
nodeList.get(i).setPsi(cachedPsi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void bindStubsWithAst(StubbedSpine srcSpine, List<StubElement<?>> stubList, List<CompositeElement> nodeList, boolean takePsiFromStubs) {
|
||||
for (int i = firstNonFilePsiIndex; i < stubList.size(); i++) {
|
||||
StubElement<?> stub = stubList.get(i);
|
||||
CompositeElement node = nodeList.get(i);
|
||||
assert stub.getStubType() == node.getElementType() : "Stub type mismatch";
|
||||
|
||||
PsiElement psi = srcSpine.getStubPsi(i);
|
||||
assert psi != null;
|
||||
if (takePsiFromStubs) {
|
||||
node.setPsi(psi);
|
||||
} else {
|
||||
//noinspection unchecked
|
||||
((StubBase)stub).setPsi(psi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,8 +217,7 @@ final class FileTrees {
|
||||
return "FileTrees{" +
|
||||
"stub=" + (myStub == null ? "noRef" : derefStub()) +
|
||||
", AST=" + (myTreeElementPointer == null ? "noRef" : derefTreeElement()) +
|
||||
", astLoaded=" + astLoaded +
|
||||
", useStrongRefs=" + useStrongRefs +
|
||||
", useSpineRefs=" + useSpineRefs() +
|
||||
'}' ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package com.intellij.psi.impl.source;
|
||||
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.ide.util.PsiNavigationSupport;
|
||||
import com.intellij.lang.*;
|
||||
import com.intellij.navigation.ItemPresentation;
|
||||
@@ -14,8 +13,10 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Queryable;
|
||||
import com.intellij.openapi.util.Factory;
|
||||
import com.intellij.openapi.util.*;
|
||||
import com.intellij.openapi.util.Getter;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileWithId;
|
||||
@@ -40,7 +41,6 @@ import com.intellij.util.Function;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.PatchedWeakReference;
|
||||
import com.intellij.util.concurrency.AtomicFieldUpdater;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -66,8 +66,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
private volatile FileTrees myTrees = FileTrees.noStub(null, this);
|
||||
private boolean myInvalidated;
|
||||
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
|
||||
private AstPathPsiMap myRefToPsi;
|
||||
private final ThreadLocal<FileElement> myFileElementBeingLoaded = new ThreadLocal<>();
|
||||
protected final PsiManagerEx myManager;
|
||||
public static final Key<Boolean> BUILDING_STUB = new Key<>("Don't use stubs mark!");
|
||||
private final PsiLock myPsiLock;
|
||||
@@ -80,7 +78,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
protected PsiFileImpl(@NotNull FileViewProvider provider ) {
|
||||
myManager = (PsiManagerEx)provider.getManager();
|
||||
myViewProvider = provider;
|
||||
myRefToPsi = new AstPathPsiMap(getProject());
|
||||
myPsiLock = ((AbstractFileViewProvider) provider).getFilePsiLock();
|
||||
}
|
||||
|
||||
@@ -179,43 +176,16 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
}
|
||||
}
|
||||
|
||||
FileElement treeElement = createFileElement(viewProvider.getContents());
|
||||
treeElement.setPsi(this);
|
||||
|
||||
myFileElementBeingLoaded.set(treeElement);
|
||||
try {
|
||||
while (true) {
|
||||
FileTrees trees = myTrees;
|
||||
List<Pair<StubBasedPsiElementBase, AstPath>> bindings = calcStubAstBindings(treeElement, trees);
|
||||
|
||||
FileElement savedTree = ensureTreeElement(viewProvider, treeElement, trees, bindings);
|
||||
if (savedTree != null) {
|
||||
return savedTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
myFileElementBeingLoaded.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FileElement ensureTreeElement(@NotNull FileViewProvider viewProvider,
|
||||
@NotNull FileElement treeElement,
|
||||
@NotNull FileTrees trees,
|
||||
@NotNull List<Pair<StubBasedPsiElementBase, AstPath>> bindings) {
|
||||
synchronized (myPsiLock) {
|
||||
FileElement existing = derefTreeElement();
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
FileElement treeElement = derefTreeElement();
|
||||
if (treeElement != null) {
|
||||
return treeElement;
|
||||
}
|
||||
|
||||
if (trees != myTrees) {
|
||||
return null; // try again
|
||||
}
|
||||
treeElement = createFileElement(viewProvider.getContents());
|
||||
treeElement.setPsi(this);
|
||||
|
||||
switchFromStubToAst(bindings, trees);
|
||||
updateTrees(trees.withAst(createTreeElementPointer(treeElement)));
|
||||
updateTrees(myTrees.withAst(createTreeElementPointer(treeElement)));
|
||||
|
||||
if (LOG.isDebugEnabled() && viewProvider.isPhysical()) {
|
||||
LOG.debug("Loaded text for file " + viewProvider.getVirtualFile().getPresentableUrl());
|
||||
@@ -229,49 +199,15 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
@Override
|
||||
public StubbedSpine getStubbedSpine() {
|
||||
StubTree tree = getGreenStubTree();
|
||||
return tree != null ? tree.getSpine() : calcTreeElement().getStubbedSpine();
|
||||
}
|
||||
|
||||
private void switchFromStubToAst(List<Pair<StubBasedPsiElementBase, AstPath>> bindings, FileTrees trees) {
|
||||
if (!bindings.isEmpty() && trees.useStrongRefs) {
|
||||
List<String> psiStrings = ContainerUtil.map(bindings, pair -> pair.first.getClass().getName());
|
||||
LOG.error(this + " of " + getClass() + "; " + psiStrings);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bindings.size(); i++) {
|
||||
Pair<StubBasedPsiElementBase, AstPath> pair = bindings.get(i);
|
||||
StubBasedPsiElementBase psi = pair.first;
|
||||
AstPath path = pair.second;
|
||||
path.getNode().setPsi(psi);
|
||||
myRefToPsi.cachePsi(path, psi);
|
||||
psi.setStubIndex(i + 1);
|
||||
}
|
||||
myRefToPsi.clearStubIndexCache();
|
||||
}
|
||||
|
||||
private List<Pair<StubBasedPsiElementBase, AstPath>> calcStubAstBindings(@NotNull FileElement root, FileTrees trees) {
|
||||
final StubTree stubTree = trees.derefStub();
|
||||
if (stubTree == null || trees.astLoaded) { // don't bind green stub to AST: the PSI should already be cached in myRefToPsi
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
List<Pair<StubBase, TreeElement>> result = TreeUtil.calcStubAstBindings(stubTree, root);
|
||||
if (tree != null) return tree.getSpine();
|
||||
|
||||
AstSpine astSpine = calcTreeElement().getStubbedSpine();
|
||||
if (!myTrees.useSpineRefs()) {
|
||||
synchronized (myPsiLock) {
|
||||
return ContainerUtil.map(result, pair -> {
|
||||
StubElement stub = pair.first;
|
||||
PsiElement psi = stub.getPsi();
|
||||
assert psi != null : "Stub " + stub + " (" + stub.getClass() + ") has returned null PSI";
|
||||
AstPath path = AstPath.getNodePath((CompositeElement)pair.second);
|
||||
assert path != null : "Null path";
|
||||
return Pair.create((StubBasedPsiElementBase)psi, path);
|
||||
});
|
||||
updateTrees(myTrees.switchToSpineRefs(astSpine));
|
||||
}
|
||||
}
|
||||
catch (TreeUtil.StubBindingException e) {
|
||||
reportStubAstMismatch(e.getMessage(), stubTree);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return astSpine;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -281,15 +217,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
return type instanceof IStubFileElementType ? (IStubFileElementType)type : null;
|
||||
}
|
||||
|
||||
void reportStubAstMismatch(String message, StubTree stubTree) {
|
||||
rebuildStub();
|
||||
synchronized (myPsiLock) {
|
||||
updateTrees(myTrees.clearStub(STUB_PSI_MISMATCH));
|
||||
}
|
||||
|
||||
throw StubTreeLoader.getInstance().stubTreeAndIndexDoNotMatch(message, stubTree, this);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected FileElement createFileElement(CharSequence docText) {
|
||||
final FileElement treeElement;
|
||||
@@ -396,12 +323,13 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
PsiFileImpl clone = BlockSupportImpl.getFileCopy(this, providerCopy);
|
||||
copyCopyableDataTo(clone);
|
||||
|
||||
clone.myRefToPsi = new AstPathPsiMap(getProject());
|
||||
if (getTreeElement() != null) {
|
||||
// not set by provider in clone
|
||||
final FileElement treeClone = (FileElement)calcTreeElement().clone();
|
||||
clone.setTreeElementPointer(treeClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963)
|
||||
treeClone.setPsi(clone);
|
||||
} else {
|
||||
clone.setTreeElementPointer(null);
|
||||
}
|
||||
|
||||
if (viewProvider.isEventSystemEnabled()) {
|
||||
@@ -583,8 +511,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
|
||||
DebugUtil.performPsiModification("onContentReload", () -> {
|
||||
synchronized (myPsiLock) {
|
||||
myRefToPsi.invalidatePsi();
|
||||
|
||||
FileElement treeElement = derefTreeElement();
|
||||
if (treeElement != null) {
|
||||
treeElement.detachFromFile();
|
||||
@@ -628,13 +554,12 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
public StubTree getStubTree() {
|
||||
assertReadAccessAllowed();
|
||||
|
||||
if (myTrees.astLoaded && !mayReloadStub()) return null;
|
||||
if (Boolean.TRUE.equals(getUserData(BUILDING_STUB))) return null;
|
||||
if (getTreeElement() != null) return null;
|
||||
|
||||
final StubTree derefd = derefStub();
|
||||
if (derefd != null) return derefd;
|
||||
|
||||
if (getElementTypeForStubBuilder() == null) return null;
|
||||
if (Boolean.TRUE.equals(getUserData(BUILDING_STUB)) || getElementTypeForStubBuilder() == null) return null;
|
||||
|
||||
final VirtualFile vFile = getVirtualFile();
|
||||
if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) return null;
|
||||
@@ -645,7 +570,7 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
final List<Pair<IStubFileElementType, PsiFile>> roots = StubTreeBuilder.getStubbedRoots(viewProvider);
|
||||
|
||||
synchronized (myPsiLock) {
|
||||
if (!mayLoadExclusiveStub()) return null;
|
||||
if (getTreeElement() != null) return null;
|
||||
|
||||
final StubTree derefdOnLock = derefStub();
|
||||
if (derefdOnLock != null) return derefdOnLock;
|
||||
@@ -664,66 +589,31 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
return null;
|
||||
}
|
||||
|
||||
// first, set all references from stubs to existing PSI (in AST or AstPathPsiMap)
|
||||
Map<PsiFileImpl, StubTree> bindings = prepareAllStubTrees(roots, stubRoots);
|
||||
StubTree result = bindings.get(this);
|
||||
assert result != null : "Current file not in root list: " + roots + ", vp=" + viewProvider;
|
||||
|
||||
// now stubs can be safely published
|
||||
for (PsiFileImpl eachPsiRoot : bindings.keySet()) {
|
||||
FileTrees trees = eachPsiRoot.myTrees;
|
||||
StubTree stub = bindings.get(eachPsiRoot);
|
||||
FileElement ast = trees.derefTreeElement();
|
||||
eachPsiRoot.updateTrees(ast == null ? trees.withExclusiveStub(stub, bindings.keySet()) : trees.withGreenStub(stub, eachPsiRoot));
|
||||
StubTree result = null;
|
||||
for (int i = 0; i < roots.size(); i++) {
|
||||
PsiFileImpl eachPsiRoot = (PsiFileImpl)roots.get(i).second;
|
||||
if (eachPsiRoot.derefStub() == null) {
|
||||
StubTree stubTree = eachPsiRoot.setStubTree(stubRoots[i]);
|
||||
if (eachPsiRoot == this) {
|
||||
result = stubTree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert result != null : "Current file not in root list: " + roots + ", vp=" + viewProvider;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mayLoadExclusiveStub() {
|
||||
return getTreeElement() == null && !hasUnbindableCachedPsi();
|
||||
}
|
||||
|
||||
private static Map<PsiFileImpl, StubTree> prepareAllStubTrees(List<Pair<IStubFileElementType, PsiFile>> roots, PsiFileStub[] rootStubs) {
|
||||
Map<PsiFileImpl, StubTree> bindings = ContainerUtil.newIdentityHashMap();
|
||||
for (int i = 0; i < roots.size(); i++) {
|
||||
PsiFileImpl eachPsiRoot = (PsiFileImpl)roots.get(i).second;
|
||||
//noinspection unchecked
|
||||
((StubBase)rootStubs[i]).setPsi(eachPsiRoot);
|
||||
StubTree stubTree = new StubTree(rootStubs[i]);
|
||||
FileElement fileElement = eachPsiRoot.getTreeElement();
|
||||
stubTree.setDebugInfo("created in getStubTree(), with AST = " + (fileElement != null));
|
||||
if (fileElement != null) {
|
||||
// Set references from these stubs to AST, because:
|
||||
// Stub index might call getStubTree on main PSI file, but then use getPlainListFromAllRoots and return stubs from another file.
|
||||
// Even if that file already has AST, stub.getPsi() should be the same as in AST
|
||||
TreeUtil.bindStubsToTree(stubTree, fileElement);
|
||||
eachPsiRoot.myRefToPsi.clearStubIndexCache();
|
||||
bindings.put(eachPsiRoot, stubTree);
|
||||
} else if (eachPsiRoot.derefStub() == null && eachPsiRoot.mayLoadExclusiveStub()) {
|
||||
eachPsiRoot.bindStubsToCachedPsi(stubTree);
|
||||
bindings.put(eachPsiRoot, stubTree);
|
||||
}
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
private boolean mayReloadStub() {
|
||||
if (getTreeElement() != null || useStrongRefs()) {
|
||||
return false;
|
||||
}
|
||||
StubTreeLoader loader = StubTreeLoader.getInstance();
|
||||
if (loader != null && loader.isStubReloadingProhibited()) {
|
||||
return false;
|
||||
}
|
||||
return !hasUnbindableCachedPsi();
|
||||
}
|
||||
|
||||
private boolean hasUnbindableCachedPsi() {
|
||||
synchronized (myPsiLock) {
|
||||
return ContainerUtil.exists(myViewProvider.getAllFiles(),
|
||||
file -> file instanceof PsiFileImpl && ((PsiFileImpl)file).myRefToPsi.hasUnbindableCachedPsi());
|
||||
}
|
||||
@NotNull
|
||||
private StubTree setStubTree(PsiFileStub root) {
|
||||
//noinspection unchecked
|
||||
((StubBase)root).setPsi(this);
|
||||
StubTree stubTree = new StubTree(root);
|
||||
FileElement fileElement = getTreeElement();
|
||||
stubTree.setDebugInfo("created in getStubTree(), with AST = " + (fileElement != null));
|
||||
updateTrees(myTrees.withStub(stubTree, fileElement));
|
||||
return stubTree;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -738,23 +628,8 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
}
|
||||
}
|
||||
|
||||
FileTrees getFileTrees() {
|
||||
return myTrees;
|
||||
}
|
||||
|
||||
private void bindStubsToCachedPsi(StubTree stubTree) {
|
||||
myRefToPsi.getAllCachedPsi().forEach(psi -> {
|
||||
int index = psi.getStubIndex();
|
||||
if (index >= 0) {
|
||||
//noinspection unchecked
|
||||
((StubBase)stubTree.getPlainList().get(index)).setPsi(psi);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected PsiFileImpl cloneImpl(FileElement treeElementClone) {
|
||||
PsiFileImpl clone = (PsiFileImpl)super.clone();
|
||||
clone.myRefToPsi = new AstPathPsiMap(getProject());
|
||||
clone.setTreeElementPointer(treeElementClone); // should not use setTreeElement here because cloned file still have VirtualFile (SCR17963)
|
||||
treeElementClone.setPsi(clone);
|
||||
return clone;
|
||||
@@ -791,14 +666,8 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
|
||||
@NotNull
|
||||
public final FileElement calcTreeElement() {
|
||||
// Attempt to find (loaded) tree element without taking lock first.
|
||||
FileElement treeElement = getTreeElement();
|
||||
if (treeElement != null) return treeElement;
|
||||
|
||||
treeElement = myFileElementBeingLoaded.get();
|
||||
if (treeElement != null) return treeElement;
|
||||
|
||||
return loadTreeElement();
|
||||
return treeElement != null ? treeElement : loadTreeElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1026,7 +895,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
if (tree != null) {
|
||||
return tree;
|
||||
}
|
||||
assert myFileElementBeingLoaded.get() == null : "non-empty thread-local";
|
||||
FileElement fileElement = calcTreeElement();
|
||||
synchronized (myPsiLock) {
|
||||
tree = derefStub();
|
||||
@@ -1052,23 +920,14 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
|
||||
tree = new StubTree((PsiFileStub)currentStubTree);
|
||||
tree.setDebugInfo("created in calcStubTree");
|
||||
try {
|
||||
TreeUtil.bindStubsToTree(tree, fileElement);
|
||||
myRefToPsi.clearStubIndexCache();
|
||||
}
|
||||
catch (TreeUtil.StubBindingException e) {
|
||||
rebuildStub();
|
||||
throw new RuntimeException("Stub and PSI element type mismatch in " + getName(), e);
|
||||
}
|
||||
|
||||
updateTrees(myTrees.withGreenStub(tree, this));
|
||||
updateTrees(myTrees.withStub(tree, fileElement));
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildStub() {
|
||||
final void rebuildStub() {
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
if (!myManager.isDisposed()) {
|
||||
myManager.dropPsiCaches();
|
||||
@@ -1104,13 +963,10 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
|
||||
public final void beforeAstChange() {
|
||||
checkWritable();
|
||||
if (!useStrongRefs()) {
|
||||
synchronized (myPsiLock) {
|
||||
for (PsiFile root : myViewProvider.getAllFiles()) {
|
||||
if (root instanceof PsiFileImpl) {
|
||||
((PsiFileImpl)root).switchToStrongRefs();
|
||||
}
|
||||
}
|
||||
synchronized (myPsiLock) {
|
||||
FileTrees updated = myTrees.switchToStrongRefs();
|
||||
if (updated != myTrees) {
|
||||
updateTrees(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1123,42 +979,4 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
|
||||
CheckUtil.checkWritable(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void switchToStrongRefs() {
|
||||
FileElement node = calcTreeElement();
|
||||
updateTrees(myTrees.switchToStrongRefs());
|
||||
myRefToPsi.switchToStrongRefs();
|
||||
AstPath.invalidatePaths(node);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public StubBasedPsiElementBase<?> obtainPsi(@NotNull AstPath path, @NotNull Factory<StubBasedPsiElementBase<?>> creator) {
|
||||
if (useStrongRefs()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StubBasedPsiElementBase<?> psi = myRefToPsi.getCachedPsi(path);
|
||||
if (psi != null) return psi;
|
||||
|
||||
synchronized (myPsiLock) {
|
||||
if (useStrongRefs()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
psi = myRefToPsi.getCachedPsi(path);
|
||||
return psi != null ? psi : myRefToPsi.cachePsi(path, creator.create());
|
||||
}
|
||||
}
|
||||
|
||||
final AstPathPsiMap getRefToPsi() {
|
||||
return myRefToPsi;
|
||||
}
|
||||
|
||||
public final boolean useStrongRefs() {
|
||||
return myTrees.useStrongRefs;
|
||||
}
|
||||
|
||||
public boolean mayCacheAst() {
|
||||
return myFileElementBeingLoaded.get() == null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.impl.source;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.stubs.Stub;
|
||||
import com.intellij.psi.stubs.StubTree;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
class SpineRef extends SubstrateRef {
|
||||
private final PsiFileImpl myFile;
|
||||
private final int myIndex;
|
||||
|
||||
SpineRef(@NotNull PsiFileImpl file, int index) {
|
||||
myFile = file;
|
||||
myIndex = index;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ASTNode getNode() {
|
||||
return myFile.calcTreeElement().getStubbedSpine().getSpineNodes().get(myIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Stub getStub() {
|
||||
StubTree tree = myFile.getStubTree();
|
||||
return tree == null ? null : tree.getPlainList().get(myIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Stub getGreenStub() {
|
||||
StubTree tree = myFile.getGreenStubTree();
|
||||
return tree == null ? null : tree.getPlainList().get(myIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return myFile.isValid();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiFile getContainingFile() {
|
||||
return myFile;
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,13 @@ public abstract class SubstrateRef {
|
||||
public abstract ASTNode getNode();
|
||||
|
||||
@Nullable
|
||||
public Stub getStub(int stubIndex) {
|
||||
public Stub getStub() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Stub getGreenStub(int index) {
|
||||
return getStub(index);
|
||||
public Stub getGreenStub() {
|
||||
return getStub();
|
||||
}
|
||||
|
||||
public abstract boolean isValid();
|
||||
@@ -119,7 +119,7 @@ public abstract class SubstrateRef {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Stub getStub(int stubIndex) {
|
||||
public Stub getStub() {
|
||||
return myStub;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.psi.impl.source.tree;
|
||||
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.vfs.VirtualFileWithId;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.impl.source.PsiFileImpl;
|
||||
import com.intellij.psi.impl.source.SubstrateRef;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.stubs.Stub;
|
||||
import com.intellij.psi.stubs.StubTree;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A lightweight object representing a chain of node indices (among all lazy-parseable and stub-based elements)
|
||||
* allowing to restore a specific node after it's been garbage-collected and recreated.
|
||||
*
|
||||
* @author peter
|
||||
*/
|
||||
public abstract class AstPath extends SubstrateRef {
|
||||
@SuppressWarnings("ConstantForZeroLengthArrayAllocation") // need a different instance to distinguish removed children for diagnostics
|
||||
private static final CompositeElement[] REMOVED_PATH_CHILDREN = new CompositeElement[0];
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.AstPath");
|
||||
private static final Key<CompositeElement[]> PATH_CHILDREN = Key.create("PATH_CHILDREN");
|
||||
private static final Key<AstPath> NODE_PATH = Key.create("NODE_PATH");
|
||||
|
||||
@NotNull
|
||||
public abstract PsiFileImpl getContainingFile();
|
||||
|
||||
@NotNull
|
||||
public abstract CompositeElement getNode();
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return getContainingFile().isValid();
|
||||
}
|
||||
|
||||
protected abstract int getDepth();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Stub getStub(int stubIndex) {
|
||||
if (stubIndex < 0) return null;
|
||||
|
||||
StubTree stubTree = getFileStubTree();
|
||||
return stubTree == null ? null : stubTree.getPlainList().get(stubIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract StubTree getFileStubTree();
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Stub getGreenStub(int stubIndex) {
|
||||
if (stubIndex < 0) return null;
|
||||
|
||||
StubTree stubTree = getContainingFile().getGreenStubTree();
|
||||
return stubTree == null ? null : stubTree.getPlainList().get(stubIndex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AstPath getNodePath(@NotNull CompositeElement node) {
|
||||
if (node instanceof FileElement) {
|
||||
PsiElement psi = node.getCachedPsi();
|
||||
if (!(psi instanceof PsiFileImpl)) return null;
|
||||
|
||||
PsiFileImpl file = (PsiFileImpl)psi;
|
||||
if (!(file.getVirtualFile() instanceof VirtualFileWithId) || file.getElementTypeForStubBuilder() == null) {
|
||||
return null;
|
||||
}
|
||||
return new RootPath(file);
|
||||
}
|
||||
|
||||
return node.getUserData(NODE_PATH);
|
||||
}
|
||||
|
||||
static void cacheNodePaths(@NotNull LazyParseableElement parent, @Nullable TreeElement child, @Nullable AstPath parentPath) {
|
||||
if (parentPath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int depth = parentPath.getDepth() + 1;
|
||||
|
||||
final List<CompositeElement> children = ContainerUtil.newArrayList();
|
||||
while (child != null) {
|
||||
child.acceptTree(new RecursiveTreeElementWalkingVisitor(false) {
|
||||
@Override
|
||||
public void visitComposite(CompositeElement composite) {
|
||||
boolean lazy = composite instanceof LazyParseableElement;
|
||||
if (lazy || composite.getElementType() instanceof IStubElementType) {
|
||||
int index = children.size();
|
||||
composite.putUserData(NODE_PATH, depth % 4 == 0 ? new MilestoneChildPath(parentPath, index, depth) : new ChildPath(parentPath, index));
|
||||
children.add(composite);
|
||||
}
|
||||
|
||||
if (!lazy) {
|
||||
super.visitComposite(composite);
|
||||
}
|
||||
}
|
||||
});
|
||||
child = child.getTreeNext();
|
||||
}
|
||||
|
||||
parent.putUserData(PATH_CHILDREN, children.isEmpty() ? CompositeElement.EMPTY_ARRAY : children.toArray(CompositeElement.EMPTY_ARRAY));
|
||||
|
||||
for (CompositeElement each : children) {
|
||||
if (each instanceof LazyParseableElement && ((LazyParseableElement)each).isParsed()) {
|
||||
cacheNodePaths((LazyParseableElement)each, each.getFirstChildNode(), getNodePath(each));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void invalidatePaths(@NotNull LazyParseableElement scope) {
|
||||
CompositeElement[] children = scope.getUserData(PATH_CHILDREN);
|
||||
if (children == null) return;
|
||||
|
||||
scope.putUserData(PATH_CHILDREN, REMOVED_PATH_CHILDREN);
|
||||
for (CompositeElement child : children) {
|
||||
child.putUserData(NODE_PATH, null);
|
||||
assertConsistency(child.getCachedPsi());
|
||||
if (child instanceof LazyParseableElement) {
|
||||
invalidatePaths((LazyParseableElement)child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertConsistency(PsiElement cachedPsi) {
|
||||
if (cachedPsi instanceof StubBasedPsiElementBase &&
|
||||
((StubBasedPsiElementBase)cachedPsi).getSubstrateRef() instanceof AstPath) {
|
||||
LOG.error("Expected strong reference at " + cachedPsi +
|
||||
" of " + cachedPsi.getClass() +
|
||||
" and " + ((StubBasedPsiElementBase)cachedPsi).getElementType());
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChildPath extends AstPath {
|
||||
private final AstPath myParent;
|
||||
private final int myIndex;
|
||||
|
||||
ChildPath(@NotNull AstPath parent, int index) {
|
||||
myParent = parent;
|
||||
myIndex = index;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiFileImpl getContainingFile() {
|
||||
return myParent.getContainingFile();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompositeElement getNode() {
|
||||
CompositeElement parentNode = myParent.getNode();
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
parentNode.getFirstChildNode(); // expand chameleons, populate PATH_CHILDREN array
|
||||
CompositeElement[] children = parentNode.getUserData(PATH_CHILDREN);
|
||||
boolean removed = children == REMOVED_PATH_CHILDREN;
|
||||
if (children == null || removed) {
|
||||
throw reportMissingChildren(parentNode, removed);
|
||||
}
|
||||
if (myIndex >= children.length) {
|
||||
throw new AssertionError(myIndex + " >= " + children.length + "; " + parentNode + " of " + parentNode.getClass());
|
||||
}
|
||||
return children[myIndex];
|
||||
}
|
||||
|
||||
private AssertionError reportMissingChildren(CompositeElement parentNode, boolean removed) {
|
||||
String message = "No path children in " + parentNode + " of " + parentNode.getClass() + "; removed=" + removed;
|
||||
|
||||
PsiFileImpl file = getContainingFile();
|
||||
message += "\n file: " + file + " of " + file.getClass() + "; physical=" + file.isPhysical() + "; useStrongRefs=" + file.useStrongRefs();
|
||||
|
||||
FileElement fileElement = file.getTreeElement();
|
||||
message += "\n ast=" + fileElement;
|
||||
if (fileElement != null) {
|
||||
CompositeElement[] rootChildren = fileElement.getUserData(PATH_CHILDREN);
|
||||
message += "; root.children=" + (rootChildren == REMOVED_PATH_CHILDREN ? "removed" : rootChildren == null ? "null" : "nonNull");
|
||||
}
|
||||
return new AssertionError(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDepth() {
|
||||
return 1 + myParent.getDepth();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StubTree getFileStubTree() {
|
||||
return myParent.getFileStubTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ChildPath)) return false;
|
||||
|
||||
ChildPath path = (ChildPath)o;
|
||||
return myIndex == path.myIndex && myParent.equals(path.myParent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * myParent.hashCode() + myIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MilestoneChildPath extends ChildPath {
|
||||
private final int myDepth;
|
||||
private final PsiFileImpl myFile;
|
||||
private volatile WeakReference<CompositeElement> myNode;
|
||||
|
||||
MilestoneChildPath(@NotNull AstPath parent, int index, int depth) {
|
||||
super(parent, index);
|
||||
myDepth = depth;
|
||||
myFile = parent.getContainingFile();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompositeElement getNode() {
|
||||
CompositeElement node = SoftReference.dereference(myNode);
|
||||
if (node == null) {
|
||||
node = super.getNode();
|
||||
if (myFile.mayCacheAst()) {
|
||||
myNode = new WeakReference<>(node);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StubTree getFileStubTree() {
|
||||
return SoftReference.dereference(myNode) == null ? myFile.getStubTree() : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiFileImpl getContainingFile() {
|
||||
return myFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDepth() {
|
||||
return myDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RootPath extends AstPath {
|
||||
private final PsiFileImpl myFile;
|
||||
|
||||
RootPath(@NotNull PsiFileImpl file) {
|
||||
myFile = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof RootPath && myFile.equals(((RootPath)o).myFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myFile.hashCode();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiFileImpl getContainingFile() {
|
||||
return myFile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CompositeElement getNode() {
|
||||
return myFile.calcTreeElement();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDepth() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected StubTree getFileStubTree() {
|
||||
return myFile.getStubTree();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,7 +57,8 @@ public class AstSpine implements StubbedSpine {
|
||||
return index >= myNodes.size() ? null : myNodes.get(index).getElementType();
|
||||
}
|
||||
|
||||
List<CompositeElement> getNodes() {
|
||||
@NotNull
|
||||
public List<CompositeElement> getSpineNodes() {
|
||||
return myNodes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.intellij.psi.impl.source.tree;
|
||||
|
||||
import com.intellij.diagnostic.ThreadDumper;
|
||||
import com.intellij.extapi.psi.ASTDelegatePsiElement;
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.lang.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -29,9 +28,10 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.psi.impl.FreeThreadedFileViewProvider;
|
||||
import com.intellij.psi.impl.source.*;
|
||||
import com.intellij.psi.impl.source.DummyHolder;
|
||||
import com.intellij.psi.impl.source.DummyHolderFactory;
|
||||
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
|
||||
import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
|
||||
import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
@@ -723,21 +723,10 @@ public class CompositeElement extends TreeElement {
|
||||
PsiElement wrapper = myWrapper;
|
||||
if (wrapper != null) return wrapper;
|
||||
|
||||
wrapper = obtainStubBasedPsi();
|
||||
if (wrapper == null) wrapper = createPsiNoLock();
|
||||
wrapper = createPsiNoLock();
|
||||
return ourPsiUpdater.compareAndSet(this, null, wrapper) ? wrapper : ObjectUtils.assertNotNull(myWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* If AST has been gced and recreated, but someone still holds a reference to a PSI, then {@link #getPsi()} should return the very same PSI object.
|
||||
* So we try to find that PSI in file's {@link AstPathPsiMap}.
|
||||
*/
|
||||
@Nullable
|
||||
private PsiElement obtainStubBasedPsi() {
|
||||
AstPath path = getElementType() instanceof IStubElementType ? AstPath.getNodePath(this) : null;
|
||||
return path == null ? null : path.getContainingFile().obtainPsi(path, () -> (StubBasedPsiElementBase<?>)createPsiNoLock());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PsiElement> T getPsi(@NotNull Class<T> clazz) {
|
||||
return LeafElement.getPsi(clazz, getPsi(), LOG);
|
||||
@@ -755,6 +744,10 @@ public class CompositeElement extends TreeElement {
|
||||
}
|
||||
|
||||
public void setPsi(@NotNull PsiElement psi) {
|
||||
PsiElement prev = myWrapper;
|
||||
if (prev != null && prev != psi) {
|
||||
DebugUtil.onInvalidated(prev);
|
||||
}
|
||||
myWrapper = psi;
|
||||
}
|
||||
|
||||
@@ -769,13 +762,6 @@ public class CompositeElement extends TreeElement {
|
||||
}
|
||||
|
||||
public void rawAddChildrenWithoutNotifications(@NotNull TreeElement first) {
|
||||
if (DebugUtil.DO_EXPENSIVE_CHECKS && !(this instanceof LazyParseableElement)) {
|
||||
PsiFileImpl file = getCachedFile(this);
|
||||
if (file != null && !file.useStrongRefs()) {
|
||||
throw new AssertionError("Attempt to modify PSI in a file with weakly-referenced AST. Possible cause: missing PomTransaction.");
|
||||
}
|
||||
}
|
||||
|
||||
final TreeElement last = getLastChildNode();
|
||||
if (last == null){
|
||||
TreeElement chainLast = rawSetParents(first, this);
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.intellij.psi.impl.source.tree;
|
||||
|
||||
import com.intellij.lang.*;
|
||||
import com.intellij.openapi.util.Getter;
|
||||
import com.intellij.openapi.util.RecursionManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.StubBuilder;
|
||||
import com.intellij.psi.impl.PsiManagerEx;
|
||||
@@ -27,12 +28,9 @@ import com.intellij.psi.stubs.IStubElementType;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.ILightStubFileElementType;
|
||||
import com.intellij.psi.tree.IStubFileElementType;
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.util.CharTable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -40,7 +38,7 @@ public class FileElement extends LazyParseableElement implements FileASTNode, Ge
|
||||
public static final FileElement[] EMPTY_ARRAY = new FileElement[0];
|
||||
private volatile CharTable myCharTable = new CharTableImpl();
|
||||
private volatile boolean myDetached;
|
||||
private volatile Reference<AstSpine> myStubbedSpine;
|
||||
private volatile AstSpine myStubbedSpine;
|
||||
|
||||
@Override
|
||||
protected PsiElement createPsiNoLock() {
|
||||
@@ -108,13 +106,17 @@ public class FileElement extends LazyParseableElement implements FileASTNode, Ge
|
||||
|
||||
@NotNull
|
||||
public final AstSpine getStubbedSpine() {
|
||||
AstSpine result = SoftReference.dereference(myStubbedSpine);
|
||||
AstSpine result = myStubbedSpine;
|
||||
if (result == null) {
|
||||
IStubFileElementType type = ((PsiFileImpl)getPsi()).getElementTypeForStubBuilder();
|
||||
PsiFileImpl file = (PsiFileImpl)getPsi();
|
||||
IStubFileElementType type = file.getElementTypeForStubBuilder();
|
||||
if (type == null) return AstSpine.EMPTY_SPINE;
|
||||
|
||||
result = new AstSpine(calcStubbedDescendants(type.getBuilder()));
|
||||
myStubbedSpine = getManager().isBatchFilesProcessingMode() ? new WeakReference<>(result) : new SoftReference<>(result);
|
||||
result = RecursionManager.doPreventingRecursion(file, false, () -> new AstSpine(calcStubbedDescendants(type.getBuilder())));
|
||||
if (result == null) {
|
||||
throw new StackOverflowError("Endless recursion prevented");
|
||||
}
|
||||
myStubbedSpine = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.intellij.psi.impl.source.tree;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Attachment;
|
||||
import com.intellij.openapi.diagnostic.LogUtil;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -200,7 +199,7 @@ public class LazyParseableElement extends CompositeElement {
|
||||
}
|
||||
|
||||
if (parsedNode != null) {
|
||||
setChildren(parsedNode, AstPath.getNodePath(this));
|
||||
setChildren(parsedNode);
|
||||
}
|
||||
|
||||
myParsed = true;
|
||||
@@ -220,10 +219,9 @@ public class LazyParseableElement extends CompositeElement {
|
||||
}
|
||||
}
|
||||
|
||||
private void setChildren(@NotNull TreeElement parsedNode, @Nullable AstPath thisPath) {
|
||||
private void setChildren(@NotNull TreeElement parsedNode) {
|
||||
ProgressManager.getInstance().executeNonCancelableSection(() -> {
|
||||
try {
|
||||
AstPath.cacheNodePaths(this, parsedNode, thisPath);
|
||||
TreeElement last = rawSetParents(parsedNode, this);
|
||||
super.setFirstChildNode(parsedNode);
|
||||
super.setLastChildNode(last);
|
||||
|
||||
@@ -16,21 +16,14 @@
|
||||
|
||||
package com.intellij.psi.impl.source.tree;
|
||||
|
||||
import com.intellij.extapi.psi.StubBasedPsiElementBase;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lexer.Lexer;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.util.Couple;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.impl.DebugUtil;
|
||||
import com.intellij.psi.stubs.StubBase;
|
||||
import com.intellij.psi.stubs.StubElement;
|
||||
import com.intellij.psi.stubs.StubTree;
|
||||
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.IStrongWhitespaceHolderElementType;
|
||||
@@ -38,7 +31,9 @@ import com.intellij.psi.tree.TokenSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class TreeUtil {
|
||||
@@ -441,45 +436,6 @@ public class TreeUtil {
|
||||
boolean isStrongElementOnRisingSlope = true;
|
||||
}
|
||||
|
||||
public static class StubBindingException extends RuntimeException {
|
||||
StubBindingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static void bindStubsToTree(@NotNull StubTree stubTree, @NotNull FileElement tree) throws StubBindingException {
|
||||
List<Pair<StubBase, TreeElement>> bindings = calcStubAstBindings(stubTree, tree);
|
||||
|
||||
for (int i = 0; i < bindings.size(); i++) {
|
||||
Pair<StubBase, TreeElement> pair = bindings.get(i);
|
||||
StubBasedPsiElementBase psi = (StubBasedPsiElementBase)pair.second.getPsi();
|
||||
//noinspection unchecked
|
||||
pair.first.setPsi(psi);
|
||||
psi.setStubIndex(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<Pair<StubBase, TreeElement>> calcStubAstBindings(@NotNull StubTree stubTree, @NotNull FileElement tree) throws StubBindingException {
|
||||
PsiFile file = (PsiFile)tree.getPsi();
|
||||
List<CompositeElement> nodes = tree.getStubbedSpine().getNodes();
|
||||
List<StubElement<?>> stubs = stubTree.getPlainList();
|
||||
if (stubs.size() != nodes.size()) {
|
||||
throw new StubBindingException("Stub list in " + file.getName() + " length differs from PSI");
|
||||
}
|
||||
|
||||
List<Pair<StubBase, TreeElement>> bindings = new ArrayList<>();
|
||||
for (int i = 1; i < stubs.size(); i++) { // start from 1 to skip file root stub
|
||||
StubBase<?> stub = (StubBase<?>)stubs.get(i);
|
||||
CompositeElement node = nodes.get(i);
|
||||
if (stub.getStubType() != node.getElementType()) {
|
||||
throw new StubBindingException("stub:" + stub + ", AST:" + node.getElementType());
|
||||
}
|
||||
bindings.add(Pair.create(stub, node));
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ASTNode skipWhitespaceAndComments(final ASTNode node, boolean forward) {
|
||||
return skipWhitespaceCommentsAndTokens(node, TokenSet.EMPTY, forward);
|
||||
|
||||
@@ -51,10 +51,6 @@ public abstract class StubTreeLoader {
|
||||
@Nullable
|
||||
public abstract ObjectStubTree readFromVFile(Project project, final VirtualFile vFile);
|
||||
|
||||
public boolean isStubReloadingProhibited() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void rebuildStubTree(VirtualFile virtualFile);
|
||||
|
||||
public abstract boolean canHaveStub(VirtualFile file);
|
||||
|
||||
@@ -189,11 +189,6 @@ public class StubTreeLoaderImpl extends StubTreeLoader {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStubReloadingProhibited() {
|
||||
return ourStubReloadingProhibited;
|
||||
}
|
||||
|
||||
private static int getCurrentTextContentLength(Project project, VirtualFile vFile, Document document, PsiFile psiFile) {
|
||||
if (vFile.getFileType().isBinary()) {
|
||||
return -1;
|
||||
|
||||
Reference in New Issue
Block a user