diff --git a/idea/src/org/jetbrains/jet/plugin/caches/resolve/IDELightClassGenerationSupport.java b/idea/src/org/jetbrains/jet/plugin/caches/resolve/IDELightClassGenerationSupport.java index f0e8e565501..b040db76cad 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/resolve/IDELightClassGenerationSupport.java +++ b/idea/src/org/jetbrains/jet/plugin/caches/resolve/IDELightClassGenerationSupport.java @@ -54,8 +54,9 @@ public class IDELightClassGenerationSupport extends LightClassGenerationSupport @NotNull @Override public LightClassConstructionContext analyzeRelevantCode(@NotNull Collection 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 diff --git a/idea/src/org/jetbrains/jet/plugin/caches/resolve/KotlinCacheManager.java b/idea/src/org/jetbrains/jet/plugin/caches/resolve/KotlinCacheManager.java index f15ac2c30f6..3e1210f15ca 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/resolve/KotlinCacheManager.java +++ b/idea/src/org/jetbrains/jet/plugin/caches/resolve/KotlinCacheManager.java @@ -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> KOTLIN_DECLARATIONS_CACHE = Key.create("KOTLIN_DECLARATIONS_CACHE"); + private static final Key> 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 { @Nullable @Override public Result compute() { - AnalyzeExhaust analyzeExhaust = AnalyzerFacadeForJVM.INSTANCE.analyzeFiles( - project, - JetFilesProvider.getInstance(project).allInScope(GlobalSearchScope.allScope(project)), - Collections.emptyList(), - Predicates.alwaysFalse() - ); - return Result.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.emptyList(), + Predicates.alwaysFalse(), + true); + } + finally { + incompleteTrace = null; + } + + return Result.create( + new KotlinDeclarationsCacheImpl(analyzeExhaust), + PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT + ); + } + } } } diff --git a/idea/testData/hierarchy/class/type/KJKHierarchy/1_kotlin.kt b/idea/testData/hierarchy/class/type/KJKHierarchy/1_kotlin.kt new file mode 100644 index 00000000000..e69cf4823e9 --- /dev/null +++ b/idea/testData/hierarchy/class/type/KJKHierarchy/1_kotlin.kt @@ -0,0 +1 @@ +public open class A \ No newline at end of file diff --git a/idea/testData/hierarchy/class/type/KJKHierarchy/2_MyClass.java b/idea/testData/hierarchy/class/type/KJKHierarchy/2_MyClass.java new file mode 100644 index 00000000000..01d870c5c7d --- /dev/null +++ b/idea/testData/hierarchy/class/type/KJKHierarchy/2_MyClass.java @@ -0,0 +1,3 @@ +public class MyClass extends A { + +} \ No newline at end of file diff --git a/idea/testData/hierarchy/class/type/KJKHierarchy/3_kotlin.kt b/idea/testData/hierarchy/class/type/KJKHierarchy/3_kotlin.kt new file mode 100644 index 00000000000..5b4b55add8c --- /dev/null +++ b/idea/testData/hierarchy/class/type/KJKHierarchy/3_kotlin.kt @@ -0,0 +1 @@ +public class B: MyClass() \ No newline at end of file diff --git a/idea/testData/hierarchy/class/type/KJKHierarchy/KJKHierarchy_verification.xml b/idea/testData/hierarchy/class/type/KJKHierarchy/KJKHierarchy_verification.xml new file mode 100644 index 00000000000..8df42ab6831 --- /dev/null +++ b/idea/testData/hierarchy/class/type/KJKHierarchy/KJKHierarchy_verification.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/idea/tests/org/jetbrains/jet/plugin/hierarchy/HierarchyTestGenerated.java b/idea/tests/org/jetbrains/jet/plugin/hierarchy/HierarchyTestGenerated.java index bd23f963245..de1332a6c05 100644 --- a/idea/tests/org/jetbrains/jet/plugin/hierarchy/HierarchyTestGenerated.java +++ b/idea/tests/org/jetbrains/jet/plugin/hierarchy/HierarchyTestGenerated.java @@ -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");