Light classes: Refactor construction

Introduce LightClassDataHolder: which now is reponsible for constructing clsDelegate
Move out light big chunk of delegate building logic out of LightClassDataProvider into LightClassBuilder
LightClassData only holds information about single class
This commit is contained in:
Pavel V. Talanov
2017-02-16 15:33:23 +03:00
parent 22fb9ec5e1
commit 00e84fb483
8 changed files with 314 additions and 280 deletions
@@ -0,0 +1,127 @@
/*
* Copyright 2010-2017 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 org.jetbrains.kotlin.asJava.builder
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import com.intellij.psi.stubs.StubElement
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
data class LightClassBuilderResult(val stub: PsiJavaFileStub, val bindingContext: BindingContext, val diagnostics: Diagnostics)
fun buildLightClass(
project: Project,
packageFqName: FqName,
files: Collection<KtFile>,
generateClassFilter: GenerationState.GenerateClassFilter,
context: LightClassConstructionContext,
generate: (state: GenerationState, files: Collection<KtFile>) -> Unit
): LightClassBuilderResult {
val javaFileStub = createJavaFileStub(project, packageFqName, files)
val bindingContext: BindingContext
val state: GenerationState
try {
val stubStack = Stack<StubElement<PsiElement>>()
@Suppress("UNCHECKED_CAST")
stubStack.push(javaFileStub as StubElement<PsiElement>)
state = GenerationState(
project,
KotlinLightClassBuilderFactory(stubStack),
context.module,
context.bindingContext,
files.toMutableList(),
CompilerConfiguration.EMPTY,
generateClassFilter,
wantsDiagnostics = false
)
state.beforeCompile()
bindingContext = state.bindingContext
generate(state, files)
val pop = stubStack.pop()
if (pop !== javaFileStub) {
LOG.error("Unbalanced stack operations: " + pop)
}
ServiceManager.getService(project, StubComputationTracker::class.java)?.onStubComputed(javaFileStub)
return LightClassBuilderResult(javaFileStub, bindingContext, state.collectedExtraJvmDiagnostics)
}
catch (e: ProcessCanceledException) {
throw e
}
catch (e: RuntimeException) {
logErrorWithOSInfo(e, packageFqName, null)
throw e
}
}
private fun createJavaFileStub(project: Project, packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), true)
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val manager = PsiManager.getInstance(project)
val virtualFile = getRepresentativeVirtualFile(files)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, virtualFile)) {
override fun getStub() = javaFileStub
override fun getPackageName() = packageFqName.asString()
override fun isPhysical() = false
}
javaFileStub.psi = fakeFile
return javaFileStub
}
private fun getRepresentativeVirtualFile(files: Collection<KtFile>): VirtualFile {
return files.first().viewProvider.virtualFile
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = if (virtualFile == null) "<null>" else virtualFile.path
LOG.error(
"Could not generate LightClass for $fqName declared in $path\n" +
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
cause
)
}
private val LOG = Logger.getInstance(LightClassDataProvider::class.java)
@@ -1,39 +0,0 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.asJava.builder
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
interface LightClassData {
val javaFileStub: PsiJavaFileStub
val extraDiagnostics: Diagnostics
}
object InvalidLightClassData: LightClassData {
override val javaFileStub: PsiJavaFileStub
get() = shouldNotBeCalled()
override val extraDiagnostics: Diagnostics
get() = shouldNotBeCalled()
private fun shouldNotBeCalled(): Nothing = throw UnsupportedOperationException("Should not be called")
}
data class LightClassDataImpl(
override val javaFileStub: PsiJavaFileStub,
override val extraDiagnostics: Diagnostics
): LightClassData
@@ -0,0 +1,97 @@
/*
* Copyright 2010-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 org.jetbrains.kotlin.asJava.builder
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.DebugUtil
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.stubs.StubElement
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.LightClassUtil.findClass
import org.jetbrains.kotlin.asJava.builder.InvalidLightClassDataHolder.javaFileStub
import org.jetbrains.kotlin.asJava.classes.getOutermostClassOrObject
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
interface LightClassDataHolder {
val javaFileStub: PsiJavaFileStub
val extraDiagnostics: Diagnostics
fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass): LightClassData
fun findData(classOrObject: KtClassOrObject): LightClassData = findData { it.findDelegate(classOrObject) }
fun findData(classFqName: FqName): LightClassData = findData { it.findDelegate(classFqName) }
}
interface LightClassData {
val clsDelegate: PsiClass
}
class LightClassDataImpl(override val clsDelegate: PsiClass) : LightClassData {
}
object InvalidLightClassDataHolder : LightClassDataHolder {
override val javaFileStub: PsiJavaFileStub
get() = shouldNotBeCalled()
override val extraDiagnostics: Diagnostics
get() = shouldNotBeCalled()
override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass) = shouldNotBeCalled()
private fun shouldNotBeCalled(): Nothing = throw UnsupportedOperationException("Should not be called")
}
class LightClassDataHolderImpl(
override val javaFileStub: PsiJavaFileStub,
override val extraDiagnostics: Diagnostics
) : LightClassDataHolder {
override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass) = findDelegate(javaFileStub).let(::LightClassDataImpl)
}
fun PsiJavaFileStub.findDelegate(classOrObject: KtClassOrObject): PsiClass {
findClass(this) {
ClsWrapperStubPsiFactory.getOriginalElement(it as StubElement<*>) == classOrObject
}?.let { return it }
val outermostClassOrObject = getOutermostClassOrObject(classOrObject)
val ktFileText: String? = try {
outermostClassOrObject.containingFile.text
}
catch (e: Exception) {
"Can't get text for outermost class"
}
val stubFileText = DebugUtil.stubTreeToString(this)
throw IllegalStateException("Couldn't get delegate for $this\nin $ktFileText\nstub: \n$stubFileText")
}
fun PsiJavaFileStub.findDelegate(classFqName: FqName): PsiClass {
return findClass(this) {
classFqName.asString() == it.qualifiedName
} ?: throw IllegalStateException("Facade class $classFqName not found; classes in Java file stub: ${collectClassNames(javaFileStub)}")
}
private fun collectClassNames(javaFileStub: PsiJavaFileStub): String {
val names = mutableListOf<String>()
LightClassUtil.findClass(javaFileStub) { cls ->
names.add(cls.qualifiedName ?: "<null>")
false
}
return names.joinToString(prefix = "[", postfix = "]")
}
@@ -16,30 +16,17 @@
package org.jetbrains.kotlin.asJava.builder package org.jetbrains.kotlin.asJava.builder
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StubElement
import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.classes.getOutermostClassOrObject import org.jetbrains.kotlin.asJava.classes.getOutermostClassOrObject
import org.jetbrains.kotlin.codegen.CompilationErrorHandler import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
import org.jetbrains.kotlin.fileClasses.getFileClassType import org.jetbrains.kotlin.fileClasses.getFileClassType
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
@@ -49,150 +36,37 @@ import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
abstract class LightClassDataProvider<T : LightClassData>( abstract class LightClassDataProvider<T : LightClassDataHolder>(
private val project: Project private val project: Project
) : CachedValueProvider<T> { ) : CachedValueProvider<T> {
abstract val files: Collection<KtFile>
abstract val packageFqName: FqName
abstract fun getContext(files: Collection<KtFile>): LightClassConstructionContext
abstract fun createLightClassData(javaFileStub: PsiJavaFileStub, bindingContext: BindingContext, extraDiagnostics: Diagnostics): T
abstract val generateClassFilter: GenerationState.GenerateClassFilter
abstract fun generate(state: GenerationState, files: Collection<KtFile>)
abstract val isLocal: Boolean abstract val isLocal: Boolean
open val valueAbsent: Boolean = false
override fun compute(): CachedValueProvider.Result<T>? { override fun compute(): CachedValueProvider.Result<T>? {
if (files.isEmpty()) return null if (valueAbsent) return null
return CachedValueProvider.Result.create( return CachedValueProvider.Result.create(
computeLightClassData(), computeLightClassData(),
if (isLocal) PsiModificationTracker.MODIFICATION_COUNT else PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT if (isLocal) PsiModificationTracker.MODIFICATION_COUNT else PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT
) )
} }
private fun computeLightClassData(): T { abstract fun computeLightClassData(): T
val packageFqName = packageFqName
val files = files
val context = getContext(files)
val javaFileStub = createJavaFileStub(packageFqName, files)
val bindingContext: BindingContext
val state: GenerationState
try {
val stubStack = Stack<StubElement<PsiElement>>()
@Suppress("UNCHECKED_CAST")
stubStack.push(javaFileStub as StubElement<PsiElement>)
state = GenerationState(
project,
KotlinLightClassBuilderFactory(stubStack),
context.module,
context.bindingContext,
files.toMutableList(),
CompilerConfiguration.EMPTY,
generateClassFilter,
wantsDiagnostics = false
)
state.beforeCompile()
bindingContext = state.bindingContext
generate(state, files)
val pop = stubStack.pop()
if (pop !== javaFileStub) {
LOG.error("Unbalanced stack operations: " + pop)
}
ServiceManager.getService(project, StubComputationTracker::class.java)?.onStubComputed(javaFileStub)
return createLightClassData(javaFileStub, bindingContext, state.collectedExtraJvmDiagnostics)
}
catch (e: ProcessCanceledException) {
throw e
}
catch (e: RuntimeException) {
logErrorWithOSInfo(e, packageFqName, null)
throw e
}
}
private fun createJavaFileStub(packageFqName: FqName, files: Collection<KtFile>): PsiJavaFileStub {
val javaFileStub = PsiJavaFileStubImpl(packageFqName.asString(), true)
javaFileStub.psiFactory = ClsWrapperStubPsiFactory.INSTANCE
val manager = PsiManager.getInstance(project)
val virtualFile = getRepresentativeVirtualFile(files)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, virtualFile)) {
override fun getStub() = javaFileStub
override fun getPackageName() = packageFqName.asString()
override fun isPhysical() = false
}
javaFileStub.psi = fakeFile
return javaFileStub
}
private fun getRepresentativeVirtualFile(files: Collection<KtFile>): VirtualFile {
return files.first().viewProvider.virtualFile
}
private fun logErrorWithOSInfo(cause: Throwable?, fqName: FqName, virtualFile: VirtualFile?) {
val path = if (virtualFile == null) "<null>" else virtualFile.path
LOG.error(
"Could not generate LightClass for $fqName declared in $path\n" +
"System: ${SystemInfo.OS_NAME} ${SystemInfo.OS_VERSION} Java Runtime: ${SystemInfo.JAVA_RUNTIME_VERSION}",
cause
)
}
companion object {
private val LOG = Logger.getInstance(LightClassDataProvider::class.java)
}
} }
class LightClassDataProviderForClassOrObject(private val classOrObject: KtClassOrObject) : class LightClassDataProviderForClassOrObject(private val classOrObject: KtClassOrObject) :
LightClassDataProvider<LightClassData>(classOrObject.project) { LightClassDataProvider<LightClassDataHolder>(classOrObject.project) {
private val file: KtFile
get() = classOrObject.containingKtFile
override val isLocal: Boolean get() = classOrObject.isLocal override val isLocal: Boolean get() = classOrObject.isLocal
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext {
return LightClassGenerationSupport.getInstance(classOrObject.project).getContextForClassOrObject(classOrObject)
}
override fun createLightClassData( override fun computeLightClassData(): LightClassDataHolder {
javaFileStub: PsiJavaFileStub, val file = classOrObject.containingKtFile
bindingContext: BindingContext,
extraDiagnostics: Diagnostics): LightClassData {
bindingContext.get(BindingContext.CLASS, classOrObject) ?: return InvalidLightClassData
val packageFqName = file.packageFqName
return LightClassDataImpl( val generateClassFilter = object : GenerationState.GenerateClassFilter() {
javaFileStub,
extraDiagnostics
)
}
override val files: Collection<KtFile>
get() = listOf(file)
override val packageFqName: FqName
get() = file.packageFqName
override val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean { override fun shouldGeneratePackagePart(jetFile: KtFile): Boolean {
return true return true
@@ -242,13 +116,24 @@ class LightClassDataProviderForClassOrObject(private val classOrObject: KtClassO
} }
} }
override fun generate(state: GenerationState, files: Collection<KtFile>) { val classConstructionContext = LightClassGenerationSupport.getInstance(classOrObject.project).getContextForClassOrObject(classOrObject)
val packageCodegen = state.factory.forPackage(packageFqName, files)
val file = classOrObject.containingKtFile val (stub, bindingContext, diagnostics) = buildLightClass(classOrObject.project, packageFqName, listOf(file), generateClassFilter, classConstructionContext) {
val packagePartType = state.fileClassesProvider.getFileClassType(file) state, files ->
val context = state.rootContext.intoPackagePart(packageCodegen.packageFragment, packagePartType, file) val packageCodegen = state.factory.forPackage(packageFqName, files)
packageCodegen.generateClassOrObject(getOutermostClassOrObject(classOrObject), context) val packagePartType = state.fileClassesProvider.getFileClassType(file)
state.factory.asList() val context = state.rootContext.intoPackagePart(packageCodegen.packageFragment, packagePartType, file)
packageCodegen.generateClassOrObject(getOutermostClassOrObject(classOrObject), context)
state.factory.asList()
}
bindingContext.get(BindingContext.CLASS, classOrObject) ?: return InvalidLightClassDataHolder
return LightClassDataHolderImpl(
stub,
diagnostics
)
} }
override fun toString(): String { override fun toString(): String {
@@ -256,28 +141,17 @@ class LightClassDataProviderForClassOrObject(private val classOrObject: KtClassO
} }
} }
sealed class LightClassDataProviderForFileFacade private constructor( sealed class LightClassDataProviderForFileFacade constructor(
protected val project: Project, protected val facadeFqName: FqName protected val project: Project, protected val facadeFqName: FqName
) : LightClassDataProvider<LightClassDataImpl>(project) { ) : LightClassDataProvider<LightClassDataHolderImpl>(project) {
override val packageFqName: FqName
get() = facadeFqName.parent()
override val isLocal: Boolean get() = false override val isLocal: Boolean get() = false
abstract val files: Collection<KtFile>
override fun getContext(files: Collection<KtFile>): LightClassConstructionContext { override val valueAbsent: Boolean
return LightClassGenerationSupport.getInstance(project).getContextForFacade(files) get() = files.isEmpty()
}
override fun createLightClassData( override fun computeLightClassData(): LightClassDataHolderImpl {
javaFileStub: PsiJavaFileStub, val generateClassFilter = object : GenerationState.GenerateClassFilter() {
bindingContext: BindingContext,
extraDiagnostics: Diagnostics
): LightClassDataImpl {
return LightClassDataImpl(javaFileStub, extraDiagnostics)
}
override val generateClassFilter: GenerationState.GenerateClassFilter
get() = object : GenerationState.GenerateClassFilter() {
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean { override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean {
return shouldGenerateClass(processingClassOrObject) return shouldGenerateClass(processingClassOrObject)
} }
@@ -295,21 +169,27 @@ sealed class LightClassDataProviderForFileFacade private constructor(
} }
} }
override fun generate(state: GenerationState, files: Collection<KtFile>) { val packageFqName = facadeFqName.parent()
if (!files.isEmpty()) {
val representativeFile = files.iterator().next() val (stub, _, diagnostics) = buildLightClass(project, packageFqName, files, generateClassFilter, LightClassGenerationSupport.getInstance(project).getContextForFacade(files)) generate@ {
val fileClassInfo = NoResolveFileClassesProvider.getFileClassInfo(representativeFile) state, files ->
if (!fileClassInfo.withJvmMultifileClass) { if (!files.isEmpty()) {
val codegen = state.factory.forPackage(representativeFile.packageFqName, files) val representativeFile = files.iterator().next()
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION) val fileClassInfo = NoResolveFileClassesProvider.getFileClassInfo(representativeFile)
state.factory.asList() if (!fileClassInfo.withJvmMultifileClass) {
return val codegen = state.factory.forPackage(representativeFile.packageFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
return@generate
}
} }
val codegen = state.factory.forMultifileClass(facadeFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
} }
val codegen = state.factory.forMultifileClass(facadeFqName, files) return LightClassDataHolderImpl(stub, diagnostics)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.asList()
} }
override fun toString(): String { override fun toString(): String {
@@ -20,7 +20,6 @@ import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Comparing
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.light.LightEmptyImplementsList import com.intellij.psi.impl.light.LightEmptyImplementsList
import com.intellij.psi.impl.light.LightModifierList import com.intellij.psi.impl.light.LightModifierList
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
@@ -30,8 +29,7 @@ import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.SLRUCache import com.intellij.util.containers.SLRUCache
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.builder.LightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassDataImpl
import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForFileFacade import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForFileFacade
import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
@@ -51,17 +49,17 @@ import javax.swing.Icon
class KtLightClassForFacade private constructor( class KtLightClassForFacade private constructor(
manager: PsiManager, manager: PsiManager,
private val facadeClassFqName: FqName, private val facadeClassFqName: FqName,
private val lightClassDataCache: CachedValue<LightClassDataImpl>, private val lightClassDataCache: CachedValue<LightClassDataHolder>,
files: Collection<KtFile> files: Collection<KtFile>
) : KtLightClassBase(manager) { ) : KtLightClassBase(manager) {
private data class StubCacheKey(val fqName: FqName, val searchScope: GlobalSearchScope) private data class StubCacheKey(val fqName: FqName, val searchScope: GlobalSearchScope)
class FacadeStubCache(private val project: Project) { class FacadeStubCache(private val project: Project) {
private inner class FacadeCacheData { private inner class FacadeCacheData {
val cache = object : SLRUCache<StubCacheKey, CachedValue<LightClassDataImpl>>(20, 30) { val cache = object : SLRUCache<StubCacheKey, CachedValue<LightClassDataHolder>>(20, 30) {
override fun createValue(key: StubCacheKey): CachedValue<LightClassDataImpl> { override fun createValue(key: StubCacheKey): CachedValue<LightClassDataHolder> {
val stubProvider = LightClassDataProviderForFileFacade.ByProjectSource(project, key.fqName, key.searchScope) val stubProvider = LightClassDataProviderForFileFacade.ByProjectSource(project, key.fqName, key.searchScope)
return CachedValuesManager.getManager(project).createCachedValue<LightClassDataImpl>(stubProvider, /*trackValue = */false) return CachedValuesManager.getManager(project).createCachedValue<LightClassDataHolder>(stubProvider, /*trackValue = */false)
} }
} }
} }
@@ -70,7 +68,7 @@ class KtLightClassForFacade private constructor(
{ CachedValueProvider.Result.create(FacadeCacheData(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) }, { CachedValueProvider.Result.create(FacadeCacheData(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT) },
/*trackValue = */ false) /*trackValue = */ false)
operator fun get(qualifiedName: FqName, searchScope: GlobalSearchScope): CachedValue<LightClassDataImpl> { operator fun get(qualifiedName: FqName, searchScope: GlobalSearchScope): CachedValue<LightClassDataHolder> {
synchronized (cachedValue) { synchronized (cachedValue) {
return cachedValue.value.cache.get(StubCacheKey(qualifiedName, searchScope)) return cachedValue.value.cache.get(StubCacheKey(qualifiedName, searchScope))
} }
@@ -197,21 +195,7 @@ class KtLightClassForFacade private constructor(
override fun copy() = KtLightClassForFacade(manager, facadeClassFqName, lightClassDataCache, files) override fun copy() = KtLightClassForFacade(manager, facadeClassFqName, lightClassDataCache, files)
override val clsDelegate: PsiClass override val clsDelegate: PsiClass
get() { get() = lightClassDataCache.value.findData(facadeClassFqName).clsDelegate
val javaFileStub = lightClassDataCache.value.javaFileStub
return LightClassUtil.findClass(javaFileStub) {
facadeClassFqName.asString() == it.qualifiedName
} ?: throw IllegalStateException("Facade class $facadeClassFqName not found; classes in Java file stub: ${collectClassNames(javaFileStub)}")
}
private fun collectClassNames(javaFileStub: PsiJavaFileStub): String {
val names = mutableListOf<String>()
LightClassUtil.findClass(javaFileStub) { cls ->
names.add(cls.qualifiedName ?: "<null>")
false
}
return names.joinToString(prefix = "[", postfix = "]")
}
override fun getNavigationElement() = files.iterator().next() override fun getNavigationElement() = files.iterator().next()
@@ -284,7 +268,7 @@ class KtLightClassForFacade private constructor(
): KtLightClassForFacade { ): KtLightClassForFacade {
// TODO: refactor, using cached value doesn't make sense for this case // TODO: refactor, using cached value doesn't make sense for this case
val cachedValue = CachedValuesManager.getManager(manager.project). val cachedValue = CachedValuesManager.getManager(manager.project).
createCachedValue<LightClassDataImpl>( createCachedValue<LightClassDataHolder>(
LightClassDataProviderForFileFacade.ByFile(manager.project, facadeClassFqName, file), /*trackValue = */false LightClassDataProviderForFileFacade.ByFile(manager.project, facadeClassFqName, file), /*trackValue = */false
) )
return KtLightClassForFacade(manager, facadeClassFqName, cachedValue, listOf(file)) return KtLightClassForFacade(manager, facadeClassFqName, cachedValue, listOf(file))
@@ -17,17 +17,16 @@
package org.jetbrains.kotlin.asJava.classes package org.jetbrains.kotlin.asJava.classes
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub import com.intellij.psi.impl.DebugUtil
import com.intellij.psi.stubs.StubElement
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.builder.LightClassData
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory import org.jetbrains.kotlin.asJava.builder.findDelegate
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
class KtLightClassForInterfaceDefaultImpls( class KtLightClassForInterfaceDefaultImpls(
classOrObject: KtClassOrObject) classOrObject: KtClassOrObject)
: KtLightClassForSourceDeclaration(classOrObject){ : KtLightClassForSourceDeclaration(classOrObject) {
override fun getQualifiedName(): String? = containingClass?.qualifiedName?.let { it + ".${JvmAbi.DEFAULT_IMPLS_CLASS_NAME}" } override fun getQualifiedName(): String? = containingClass?.qualifiedName?.let { it + ".${JvmAbi.DEFAULT_IMPLS_CLASS_NAME}" }
override fun getName() = JvmAbi.DEFAULT_IMPLS_CLASS_NAME override fun getName() = JvmAbi.DEFAULT_IMPLS_CLASS_NAME
@@ -37,11 +36,11 @@ class KtLightClassForInterfaceDefaultImpls(
return KtLightClassForInterfaceDefaultImpls(classOrObject.copy() as KtClassOrObject) return KtLightClassForInterfaceDefaultImpls(classOrObject.copy() as KtClassOrObject)
} }
override fun findDelegateClass(javaFileStub: PsiJavaFileStub): PsiClass? { override fun findLightClassData(): LightClassData {
val interfaceClass = LightClassUtil.findClass(javaFileStub) { return getLightClassDataHolder().findData {
ClsWrapperStubPsiFactory.getOriginalElement(it as StubElement<*>) == classOrObject it.findDelegate(classOrObject).findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false)
?: throw IllegalStateException("Couldn't get delegate for $this\n in ${DebugUtil.stubTreeToString(it)}")
} }
return interfaceClass?.findInnerClassByName(JvmAbi.DEFAULT_IMPLS_CLASS_NAME, false)
} }
override fun getTypeParameterList(): PsiTypeParameterList? = null override fun getTypeParameterList(): PsiTypeParameterList? = null
@@ -21,7 +21,6 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.DebugUtil
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.scope.PsiScopeProcessor import com.intellij.psi.scope.PsiScopeProcessor
import com.intellij.psi.search.SearchScope import com.intellij.psi.search.SearchScope
@@ -34,9 +33,9 @@ import com.intellij.util.containers.ContainerUtil
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.LightClassUtil import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.builder.ClsWrapperStubPsiFactory.getOriginalElement import org.jetbrains.kotlin.asJava.builder.InvalidLightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassData import org.jetbrains.kotlin.asJava.builder.LightClassData
import org.jetbrains.kotlin.asJava.builder.LightClassDataImpl import org.jetbrains.kotlin.asJava.builder.LightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForClassOrObject import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForClassOrObject
import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass
import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier
@@ -79,41 +78,24 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
abstract override fun getParent(): PsiElement? abstract override fun getParent(): PsiElement?
abstract override fun getQualifiedName(): String? abstract override fun getQualifiedName(): String?
override val clsDelegate: PsiClass by lazy(LazyThreadSafetyMode.PUBLICATION) { override val clsDelegate: PsiClass get() = lightClassData.clsDelegate
val javaFileStub = getJavaFileStub()
findDelegateClass(javaFileStub) ?: run { private val lightClassData: LightClassData by lazy(LazyThreadSafetyMode.PUBLICATION) { findLightClassData() }
val outermostClassOrObject = getOutermostClassOrObject(classOrObject)
val ktFileText: String? = try {
outermostClassOrObject.containingFile.text
}
catch (e: Exception) {
"Can't get text for outermost class"
}
val stubFileText = DebugUtil.stubTreeToString(javaFileStub) open protected fun findLightClassData() = getLightClassDataHolder().findData(classOrObject)
throw IllegalStateException("Couldn't get delegate for $this\nin $ktFileText\nstub: \n$stubFileText")
}
}
protected open fun findDelegateClass(javaFileStub: PsiJavaFileStub): PsiClass? { private fun getJavaFileStub(): PsiJavaFileStub = getLightClassDataHolder().javaFileStub
return LightClassUtil.findClass(javaFileStub) {
getOriginalElement(it as StubElement<*>) == classOrObject
}
}
private fun getJavaFileStub(): PsiJavaFileStub = getLightClassData().javaFileStub
protected fun getDescriptor(): ClassDescriptor? { protected fun getDescriptor(): ClassDescriptor? {
return LightClassGenerationSupport.getInstance(project).resolveToDescriptor(classOrObject) as? ClassDescriptor return LightClassGenerationSupport.getInstance(project).resolveToDescriptor(classOrObject) as? ClassDescriptor
} }
private fun getLightClassData(): LightClassDataImpl { protected fun getLightClassDataHolder(): LightClassDataHolder {
val lightClassData = getLightClassData(classOrObject) val lightClassData = getLightClassDataHolder(classOrObject)
if (lightClassData !is LightClassDataImpl) { if (lightClassData is InvalidLightClassDataHolder) {
LOG.error("Invalid light class data for existing light class:\n$lightClassData\n${classOrObject.getElementTextWithContext()}") LOG.error("Invalid light class data for existing light class:\n$lightClassData\n${classOrObject.getElementTextWithContext()}")
} }
return lightClassData as LightClassDataImpl return lightClassData
} }
private val _containingFile: PsiFile by lazy(LazyThreadSafetyMode.PUBLICATION) { private val _containingFile: PsiFile by lazy(LazyThreadSafetyMode.PUBLICATION) {
@@ -341,7 +323,7 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
override fun getImplementsList() = _implementsList override fun getImplementsList() = _implementsList
companion object { companion object {
private val JAVA_API_STUB = Key.create<CachedValue<LightClassData>>("JAVA_API_STUB") private val JAVA_API_STUB = Key.create<CachedValue<LightClassDataHolder>>("JAVA_API_STUB")
private val jetTokenToPsiModifier = listOf( private val jetTokenToPsiModifier = listOf(
PUBLIC_KEYWORD to PsiModifier.PUBLIC, PUBLIC_KEYWORD to PsiModifier.PUBLIC,
@@ -382,11 +364,11 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
return classOrObject.getBody()?.declarations?.isEmpty() ?: true return classOrObject.getBody()?.declarations?.isEmpty() ?: true
} }
fun getLightClassData(classOrObject: KtClassOrObject): LightClassData { fun getLightClassDataHolder(classOrObject: KtClassOrObject): LightClassDataHolder {
return getLightClassCachedValue(classOrObject).value return getLightClassCachedValue(classOrObject).value
} }
fun getLightClassCachedValue(classOrObject: KtClassOrObject): CachedValue<LightClassData> { fun getLightClassCachedValue(classOrObject: KtClassOrObject): CachedValue<LightClassDataHolder> {
var value = var value =
getOutermostClassOrObject(classOrObject).getUserData(JAVA_API_STUB) // stub computed for outer class can be used for inner/nested getOutermostClassOrObject(classOrObject).getUserData(JAVA_API_STUB) // stub computed for outer class can be used for inner/nested
?: classOrObject.getUserData(JAVA_API_STUB) ?: classOrObject.getUserData(JAVA_API_STUB)
@@ -18,9 +18,9 @@ package org.jetbrains.kotlin.asJava
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.asJava.builder.LightClassDataImpl import org.jetbrains.kotlin.asJava.builder.InvalidLightClassDataHolder
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory.cast import org.jetbrains.kotlin.diagnostics.DiagnosticFactory.cast
@@ -42,7 +42,11 @@ fun getJvmSignatureDiagnostics(element: PsiElement, otherDiagnostics: Diagnostic
} }
fun getDiagnosticsForClass(ktClassOrObject: KtClassOrObject): Diagnostics { fun getDiagnosticsForClass(ktClassOrObject: KtClassOrObject): Diagnostics {
return (KtLightClassForSourceDeclaration.getLightClassData(ktClassOrObject) as? LightClassDataImpl)?.extraDiagnostics ?: Diagnostics.EMPTY val lightClassDataHolder = KtLightClassForSourceDeclaration.getLightClassDataHolder(ktClassOrObject)
if (lightClassDataHolder is InvalidLightClassDataHolder) {
return Diagnostics.EMPTY
}
return lightClassDataHolder.extraDiagnostics
} }
fun doGetDiagnostics(): Diagnostics? { fun doGetDiagnostics(): Diagnostics? {