Incomplete analysis results are available for light class generation
If we have the following classes
class A // Kotlin
class B extends A {} // Java
class C : B() // Kotlin
The analysis runs into infinite recursion, because C needs all members of B (to compute overrides),
and B needs all members of A, and A is not available from KotlinCacheManager.getDeclarationsFromProject() -- it is being computed right now,
so the analysis runs again...
Our workaround is:
* when analysis starts put its trace into user data on the project
* when a light class is about to generate a stub, it requests a trace, and if there is such an incomplete trace, it is returned
* the logic of TopDownAnalyzer that works in descending order (down the class hierarchy) guarantees that all the superclasses are already processed
This commit is contained in:
+3
-2
@@ -54,8 +54,9 @@ public class IDELightClassGenerationSupport extends LightClassGenerationSupport
|
||||
@NotNull
|
||||
@Override
|
||||
public LightClassConstructionContext analyzeRelevantCode(@NotNull Collection<JetFile> files) {
|
||||
KotlinDeclarationsCache cache = KotlinCacheManager.getInstance(project).getDeclarationsFromProject();
|
||||
return new LightClassConstructionContext(cache.getBindingContext(), null);
|
||||
KotlinCacheManager cacheManager = KotlinCacheManager.getInstance(project);
|
||||
KotlinDeclarationsCache declarationsCache = cacheManager.getPossiblyIncompleteDeclarationsForLightClassGeneration();
|
||||
return new LightClassConstructionContext(declarationsCache.getBindingContext(), null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -31,6 +31,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
|
||||
import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext;
|
||||
import org.jetbrains.jet.lang.resolve.BindingTrace;
|
||||
import org.jetbrains.jet.lang.resolve.BindingTraceContext;
|
||||
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM;
|
||||
import org.jetbrains.jet.lang.resolve.java.JetFilesProvider;
|
||||
|
||||
@@ -42,21 +45,34 @@ public class KotlinCacheManager {
|
||||
return ServiceManager.getService(project, KotlinCacheManager.class);
|
||||
}
|
||||
|
||||
private final Key<CachedValue<KotlinDeclarationsCache>> KOTLIN_DECLARATIONS_CACHE = Key.create("KOTLIN_DECLARATIONS_CACHE");
|
||||
private static final Key<CachedValue<KotlinDeclarationsCache>> KOTLIN_DECLARATIONS_CACHE = Key.create("KOTLIN_DECLARATIONS_CACHE");
|
||||
|
||||
private final Project project;
|
||||
private final Object declarationAnalysisLock = new Object();
|
||||
|
||||
private BindingTrace incompleteTrace;
|
||||
|
||||
public KotlinCacheManager(@NotNull Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KotlinDeclarationsCache getDeclarationsFromProject() {
|
||||
private KotlinDeclarationsCache getDeclarations(boolean allowIncomplete) {
|
||||
// To prevent dead locks, the lock below must be obtained only inside a read action
|
||||
ApplicationManager.getApplication().assertReadAccessAllowed();
|
||||
synchronized (declarationAnalysisLock) {
|
||||
if (allowIncomplete) {
|
||||
if (incompleteTrace != null) {
|
||||
return new KotlinDeclarationsCache() {
|
||||
@NotNull
|
||||
@Override
|
||||
public BindingContext getBindingContext() {
|
||||
return incompleteTrace.getBindingContext();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return CachedValuesManager.getManager(project).getCachedValue(
|
||||
project,
|
||||
KOTLIN_DECLARATIONS_CACHE,
|
||||
@@ -66,21 +82,60 @@ public class KotlinCacheManager {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KotlinDeclarationsCache getDeclarationsFromProject() {
|
||||
return getDeclarations(false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KotlinDeclarationsCache getPossiblyIncompleteDeclarationsForLightClassGeneration() {
|
||||
/*
|
||||
* If we have the following classes
|
||||
*
|
||||
* class A // Kotlin
|
||||
* class B extends A {} // Java
|
||||
* class C : B() // Kotlin
|
||||
*
|
||||
* The analysis runs into infinite recursion, because
|
||||
* C needs all members of B (to compute overrides),
|
||||
* and B needs all members of A,
|
||||
* and A is not available from KotlinCacheManager.getDeclarationsFromProject() -- it is being computed right now,
|
||||
* so the analysis runs again...
|
||||
*
|
||||
* Our workaround is to return partially complete results when we generate light classes
|
||||
*/
|
||||
return getDeclarations(true);
|
||||
}
|
||||
|
||||
private class KotlinDeclarationsCacheProvider implements CachedValueProvider<KotlinDeclarationsCache> {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<KotlinDeclarationsCache> compute() {
|
||||
AnalyzeExhaust analyzeExhaust = AnalyzerFacadeForJVM.INSTANCE.analyzeFiles(
|
||||
project,
|
||||
JetFilesProvider.getInstance(project).allInScope(GlobalSearchScope.allScope(project)),
|
||||
Collections.<AnalyzerScriptParameter>emptyList(),
|
||||
Predicates.<PsiFile>alwaysFalse()
|
||||
);
|
||||
return Result.<KotlinDeclarationsCache>create(
|
||||
new KotlinDeclarationsCacheImpl(analyzeExhaust),
|
||||
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
|
||||
);
|
||||
}
|
||||
// This lock is already acquired by the calling method,
|
||||
// but we put it here to guard for the case of further modifications
|
||||
synchronized (declarationAnalysisLock) {
|
||||
BindingTraceContext trace = new BindingTraceContext();
|
||||
|
||||
incompleteTrace = trace;
|
||||
AnalyzeExhaust analyzeExhaust;
|
||||
try {
|
||||
analyzeExhaust = AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
|
||||
project,
|
||||
JetFilesProvider.getInstance(project).allInScope(GlobalSearchScope.allScope(project)),
|
||||
trace,
|
||||
Collections.<AnalyzerScriptParameter>emptyList(),
|
||||
Predicates.<PsiFile>alwaysFalse(),
|
||||
true);
|
||||
}
|
||||
finally {
|
||||
incompleteTrace = null;
|
||||
}
|
||||
|
||||
return Result.<KotlinDeclarationsCache>create(
|
||||
new KotlinDeclarationsCacheImpl(analyzeExhaust),
|
||||
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
public open class A<caret>
|
||||
@@ -0,0 +1,3 @@
|
||||
public class MyClass<caret> extends A {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
public class B: MyClass()
|
||||
@@ -0,0 +1,7 @@
|
||||
<node text="Object (java.lang)">
|
||||
<node text="A ()" base="true">
|
||||
<node text="MyClass ()">
|
||||
<node text="B ()"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
@@ -133,6 +133,11 @@ public class HierarchyTestGenerated extends AbstractHierarchyTest {
|
||||
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/JavaFromKotlinForKotlinClass");
|
||||
}
|
||||
|
||||
@TestMetadata("KJKHierarchy")
|
||||
public void testKJKHierarchy() throws Exception {
|
||||
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/KJKHierarchy");
|
||||
}
|
||||
|
||||
@TestMetadata("KotlinFromJava")
|
||||
public void testKotlinFromJava() throws Exception {
|
||||
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/KotlinFromJava");
|
||||
|
||||
Reference in New Issue
Block a user