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
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public LightClassConstructionContext analyzeRelevantCode(@NotNull Collection<JetFile> files) {
|
public LightClassConstructionContext analyzeRelevantCode(@NotNull Collection<JetFile> files) {
|
||||||
KotlinDeclarationsCache cache = KotlinCacheManager.getInstance(project).getDeclarationsFromProject();
|
KotlinCacheManager cacheManager = KotlinCacheManager.getInstance(project);
|
||||||
return new LightClassConstructionContext(cache.getBindingContext(), null);
|
KotlinDeclarationsCache declarationsCache = cacheManager.getPossiblyIncompleteDeclarationsForLightClassGeneration();
|
||||||
|
return new LightClassConstructionContext(declarationsCache.getBindingContext(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
|
import org.jetbrains.jet.analyzer.AnalyzeExhaust;
|
||||||
import org.jetbrains.jet.lang.resolve.AnalyzerScriptParameter;
|
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.AnalyzerFacadeForJVM;
|
||||||
import org.jetbrains.jet.lang.resolve.java.JetFilesProvider;
|
import org.jetbrains.jet.lang.resolve.java.JetFilesProvider;
|
||||||
|
|
||||||
@@ -42,21 +45,34 @@ public class KotlinCacheManager {
|
|||||||
return ServiceManager.getService(project, KotlinCacheManager.class);
|
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 Project project;
|
||||||
private final Object declarationAnalysisLock = new Object();
|
private final Object declarationAnalysisLock = new Object();
|
||||||
|
|
||||||
|
private BindingTrace incompleteTrace;
|
||||||
|
|
||||||
public KotlinCacheManager(@NotNull Project project) {
|
public KotlinCacheManager(@NotNull Project project) {
|
||||||
this.project = project;
|
this.project = project;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public KotlinDeclarationsCache getDeclarationsFromProject() {
|
private KotlinDeclarationsCache getDeclarations(boolean allowIncomplete) {
|
||||||
// To prevent dead locks, the lock below must be obtained only inside a read action
|
// To prevent dead locks, the lock below must be obtained only inside a read action
|
||||||
ApplicationManager.getApplication().assertReadAccessAllowed();
|
ApplicationManager.getApplication().assertReadAccessAllowed();
|
||||||
synchronized (declarationAnalysisLock) {
|
synchronized (declarationAnalysisLock) {
|
||||||
|
if (allowIncomplete) {
|
||||||
|
if (incompleteTrace != null) {
|
||||||
|
return new KotlinDeclarationsCache() {
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public BindingContext getBindingContext() {
|
||||||
|
return incompleteTrace.getBindingContext();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return CachedValuesManager.getManager(project).getCachedValue(
|
return CachedValuesManager.getManager(project).getCachedValue(
|
||||||
project,
|
project,
|
||||||
KOTLIN_DECLARATIONS_CACHE,
|
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> {
|
private class KotlinDeclarationsCacheProvider implements CachedValueProvider<KotlinDeclarationsCache> {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Result<KotlinDeclarationsCache> compute() {
|
public Result<KotlinDeclarationsCache> compute() {
|
||||||
AnalyzeExhaust analyzeExhaust = AnalyzerFacadeForJVM.INSTANCE.analyzeFiles(
|
// This lock is already acquired by the calling method,
|
||||||
project,
|
// but we put it here to guard for the case of further modifications
|
||||||
JetFilesProvider.getInstance(project).allInScope(GlobalSearchScope.allScope(project)),
|
synchronized (declarationAnalysisLock) {
|
||||||
Collections.<AnalyzerScriptParameter>emptyList(),
|
BindingTraceContext trace = new BindingTraceContext();
|
||||||
Predicates.<PsiFile>alwaysFalse()
|
|
||||||
);
|
|
||||||
return Result.<KotlinDeclarationsCache>create(
|
|
||||||
new KotlinDeclarationsCacheImpl(analyzeExhaust),
|
|
||||||
PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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");
|
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/JavaFromKotlinForKotlinClass");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("KJKHierarchy")
|
||||||
|
public void testKJKHierarchy() throws Exception {
|
||||||
|
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/KJKHierarchy");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("KotlinFromJava")
|
@TestMetadata("KotlinFromJava")
|
||||||
public void testKotlinFromJava() throws Exception {
|
public void testKotlinFromJava() throws Exception {
|
||||||
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/KotlinFromJava");
|
doTypeClassHierarchyTest("idea/testData/hierarchy/class/type/KotlinFromJava");
|
||||||
|
|||||||
Reference in New Issue
Block a user