Implement light classes for Kotlin scripts

This commit is contained in:
Vyacheslav Gerasimov
2017-09-04 15:30:47 +03:00
parent 26cbcfa1ac
commit 89257e6397
25 changed files with 495 additions and 139 deletions
@@ -22,12 +22,10 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.asJava.builder.*
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForScript
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
typealias LightClassBuilder = (LightClassConstructionContext) -> LightClassBuilderResult
@@ -38,6 +36,8 @@ abstract class LightClassGenerationSupport {
abstract fun createDataHolderForFacade(files: Collection<KtFile>, builder: LightClassBuilder): LightClassDataHolder.ForFacade
abstract fun createDataHolderForScript(script: KtScript, builder: LightClassBuilder): LightClassDataHolder.ForScript
abstract fun findClassOrObjectDeclarations(fqName: FqName, searchScope: GlobalSearchScope): Collection<KtClassOrObject>
/*
@@ -61,6 +61,8 @@ abstract class LightClassGenerationSupport {
abstract fun getLightClass(classOrObject: KtClassOrObject): KtLightClass?
abstract fun getLightClassForScript(script: KtScript): KtLightClassForScript?
abstract fun resolveToDescriptor(declaration: KtDeclaration): DeclarationDescriptor?
abstract fun analyze(element: KtElement): BindingContext
@@ -69,6 +71,8 @@ abstract class LightClassGenerationSupport {
abstract fun getFacadeClasses(facadeFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass>
abstract fun getScriptClasses(scriptFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass>
abstract fun getKotlinInternalClasses(fqName: FqName, scope: GlobalSearchScope): Collection<PsiClass>
abstract fun getFacadeClassesInPackage(packageFqName: FqName, scope: GlobalSearchScope): Collection<PsiClass>
@@ -53,6 +53,10 @@ interface LightClassDataHolder {
interface ForFacade : LightClassDataHolder {
fun findDataForFacade(classFqName: FqName): LightClassData = findData { it.findDelegate(classFqName) }
}
interface ForScript : ForClass {
fun findDataForScript(scriptFqName: FqName): LightClassData = findData { it.findDelegate(scriptFqName) }
}
}
interface LightClassData {
@@ -85,7 +89,7 @@ object InvalidLightClassDataHolder : LightClassDataHolder.ForClass {
class LightClassDataHolderImpl(
override val javaFileStub: PsiJavaFileStub,
override val extraDiagnostics: Diagnostics
) : LightClassDataHolder.ForClass, LightClassDataHolder.ForFacade {
) : LightClassDataHolder.ForClass, LightClassDataHolder.ForFacade, LightClassDataHolder.ForScript {
override fun findData(findDelegate: (PsiJavaFileStub) -> PsiClass) = findDelegate(javaFileStub).let(::LightClassDataImpl)
}
@@ -128,6 +128,31 @@ sealed class LightClassDataProviderForFileFacade constructor(
}
class LightClassDataProviderForScript(private val script: KtScript) : CachedValueProvider<LightClassDataHolder.ForScript> {
private fun computeLightClassData(): LightClassDataHolder.ForScript {
return LightClassGenerationSupport.getInstance(script.project).createDataHolderForScript(script) {
constructionContext ->
buildLightClass(
script.fqName.parent(),
listOf(script.containingKtFile),
ClassFilterForScript(script),
constructionContext
) generate@ {
state, files ->
val scriptFile = files.first()
val codegen = state.factory.forPackage(scriptFile.packageFqName, files)
codegen.generate(CompilationErrorHandler.THROW_EXCEPTION)
state.factory.done()
}
}
}
override fun compute(): CachedValueProvider.Result<LightClassDataHolder.ForScript>? =
CachedValueProvider.Result.create(computeLightClassData(), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT)
override fun toString(): String = this::class.java.name + " for ${script.fqName}"
}
interface StubComputationTracker {
fun onStubComputed(javaFileStub: PsiJavaFileStub, context: LightClassConstructionContext)
}
@@ -181,3 +206,17 @@ object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(ktFile: KtFile) = true
override fun shouldGenerateScript(script: KtScript) = false
}
private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() {
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean =
shouldGenerateClass(processingClassOrObject)
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean =
processingClassOrObject.isAncestor(script, true)
override fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject): Boolean = true
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile
override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script
}
@@ -0,0 +1,161 @@
/*
* 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.classes
import com.intellij.openapi.util.Comparing
import com.intellij.openapi.util.Key
import com.intellij.psi.*
import com.intellij.psi.impl.light.LightEmptyImplementsList
import com.intellij.psi.impl.light.LightModifierList
import com.intellij.psi.util.CachedValue
import com.intellij.psi.util.CachedValuesManager
import org.jetbrains.annotations.NonNls
import org.jetbrains.kotlin.asJava.builder.LightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForScript
import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.load.java.structure.LightClassOriginKind
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtScript
import javax.swing.Icon
class KtLightClassForScript private constructor(
val script: KtScript,
private val lightClassDataCache: CachedValue<LightClassDataHolder.ForScript>
) : KtLazyLightClass(script.manager) {
private val hashCode: Int = computeHashCode()
private val packageFqName: FqName = script.fqName.parent()
private val modifierList: PsiModifierList = LightModifierList(manager, KotlinLanguage.INSTANCE, PsiModifier.PUBLIC)
private val implementsList: LightEmptyImplementsList = LightEmptyImplementsList(manager)
private val _containingFile by lazyPub {
FakeFileForLightClass(
script.containingKtFile,
lightClass = { this },
stub = { lightClassDataCache.value.javaFileStub },
packageFqName = packageFqName)
}
override val kotlinOrigin: KtClassOrObject? get() = null
val fqName: FqName = script.fqName
override fun getModifierList() = modifierList
override fun hasModifierProperty(@NonNls name: String) = modifierList.hasModifierProperty(name)
override fun isDeprecated() = false
override fun isInterface() = false
override fun isAnnotationType() = false
override fun isEnum() = false
override fun getContainingClass() = null
override fun getContainingFile() = _containingFile
override fun hasTypeParameters() = false
override fun getTypeParameters(): Array<PsiTypeParameter> = PsiTypeParameter.EMPTY_ARRAY
override fun getTypeParameterList() = null
override fun getDocComment() = null
override fun getImplementsList() = implementsList
override fun getImplementsListTypes(): Array<PsiClassType> = PsiClassType.EMPTY_ARRAY
override fun getInterfaces(): Array<PsiClass> = PsiClass.EMPTY_ARRAY
override fun getOwnInnerClasses(): List<PsiClass> {
return script.declarations.filterIsInstance<KtClassOrObject>()
// workaround for ClassInnerStuffCache not supporting classes with null names, see KT-13927
// inner classes with null names can't be searched for and can't be used from java anyway
// we can't prohibit creating light classes with null names either since they can contain members
.filter { it.name != null }
.mapNotNull { KtLightClassForSourceDeclaration.create(it) }
}
override fun getInitializers(): Array<PsiClassInitializer> = PsiClassInitializer.EMPTY_ARRAY
override fun getName() = script.fqName.shortName().asString()
override fun getQualifiedName() = script.fqName.asString()
override fun isValid() = script.isValid
override fun copy() = KtLightClassForScript(script, lightClassDataCache)
override val lightClassData = lightClassDataCache.value.findDataForScript(script.fqName)
override fun getNavigationElement() = script
override fun isEquivalentTo(another: PsiElement?): Boolean =
another is PsiClass && Comparing.equal(another.qualifiedName, qualifiedName)
override fun getElementIcon(flags: Int): Icon? = throw UnsupportedOperationException("This should be done by JetIconProvider")
override fun hashCode() = hashCode
private fun computeHashCode(): Int {
var result = manager.hashCode()
result = 31 * result + script.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (other == null || this::class.java != other::class.java) {
return false
}
val lightClass = other as KtLightClassForScript
if (this === other) return true
if (this.hashCode != lightClass.hashCode) return false
if (manager != lightClass.manager) return false
if (script != lightClass.script) return false
return true
}
override fun toString() = "${KtLightClassForScript::class.java.simpleName}:${script.fqName}"
companion object {
private val JAVA_API_STUB_FOR_SCRIPT = Key.create<CachedValue<LightClassDataHolder.ForScript>>("JAVA_API_STUB_FOR_SCRIPT")
fun create(script: KtScript): KtLightClassForScript = KtLightClassForScript(script, getLightClassCachedValue(script))
fun getLightClassCachedValue(script: KtScript): CachedValue<LightClassDataHolder.ForScript> {
return script.getUserData(JAVA_API_STUB_FOR_SCRIPT) ?:
createCachedValueForScript(script).also { script.putUserData(JAVA_API_STUB_FOR_SCRIPT, it) }
}
private fun createCachedValueForScript(script: KtScript): CachedValue<LightClassDataHolder.ForScript> =
CachedValuesManager.getManager(script.project).createCachedValue(LightClassDataProviderForScript(script), false)
}
override val originKind: LightClassOriginKind get() = LightClassOriginKind.SOURCE
}
@@ -39,10 +39,7 @@ import org.jetbrains.kotlin.asJava.ImpreciseResolveResult
import org.jetbrains.kotlin.asJava.ImpreciseResolveResult.UNSURE
import org.jetbrains.kotlin.asJava.LightClassGenerationSupport
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.builder.InvalidLightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassData
import org.jetbrains.kotlin.asJava.builder.LightClassDataHolder
import org.jetbrains.kotlin.asJava.builder.LightClassDataProviderForClassOrObject
import org.jetbrains.kotlin.asJava.builder.*
import org.jetbrains.kotlin.asJava.elements.FakeFileForLightClass
import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier
import org.jetbrains.kotlin.asJava.elements.KtLightModifierList
@@ -334,7 +331,7 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
fun create(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? {
if (classOrObject.containingKtFile.isScript() || classOrObject.hasModifier(KtTokens.HEADER_KEYWORD)) {
if (classOrObject.hasModifier(KtTokens.HEADER_KEYWORD)) {
return null
}
@@ -366,10 +363,11 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
}
fun getLightClassDataHolder(classOrObject: KtClassOrObject): LightClassDataHolder.ForClass {
return getLightClassCachedValue(classOrObject).value
return classOrObject.containingKtFile.script?.let { KtLightClassForScript.getLightClassCachedValue(it).value } ?:
getLightClassCachedValue(classOrObject).value
}
fun getLightClassCachedValue(classOrObject: KtClassOrObject): CachedValue<LightClassDataHolder.ForClass> {
private fun getLightClassCachedValue(classOrObject: KtClassOrObject): CachedValue<LightClassDataHolder.ForClass> {
var value =
getOutermostClassOrObject(classOrObject).getUserData(JAVA_API_STUB) // stub computed for outer class can be used for inner/nested
?: classOrObject.getUserData(JAVA_API_STUB)
@@ -33,10 +33,7 @@ import org.jetbrains.kotlin.asJava.LightClassGenerationSupport;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNamesUtilKt;
import org.jetbrains.kotlin.psi.KtClass;
import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.psi.KtEnumEntry;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker;
import java.util.Collection;
@@ -90,7 +87,6 @@ public class JavaElementFinder extends PsiElementFinder implements KotlinFinderM
FqName qualifiedName = new FqName(qualifiedNameString);
findClassesAndObjects(qualifiedName, scope, answer);
answer.addAll(lightClassGenerationSupport.getFacadeClasses(qualifiedName, scope));
answer.addAll(lightClassGenerationSupport.getKotlinInternalClasses(qualifiedName, scope));
@@ -20,6 +20,7 @@ import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.classes.KtLightClassForScript
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
import org.jetbrains.kotlin.asJava.elements.KtLightElement
import org.jetbrains.kotlin.asJava.elements.KtLightIdentifier
@@ -52,6 +53,8 @@ fun KtFile.findFacadeClass(): KtLightClass? {
.firstOrNull { it is KtLightClassForFacade && this in it.files } as? KtLightClass
}
fun KtScript.toLightClass(): KtLightClassForScript? = LightClassGenerationSupport.getInstance(project).getLightClassForScript(this)
fun KtElement.toLightElements(): List<PsiNamedElement> =
when (this) {
is KtClassOrObject -> listOfNotNull(toLightClass())