blob: 7c1fade23dd757d5f044e97acdd4865f1864d3b5 [file] [log] [blame]
/*
* Copyright 2020 Google LLC
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* 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 com.google.devtools.ksp.processing.impl
import com.google.devtools.ksp.*
import com.google.devtools.ksp.processing.KSBuiltIns
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.Variance
import com.google.devtools.ksp.symbol.impl.binary.*
import com.google.devtools.ksp.symbol.impl.declarationsInSourceOrder
import com.google.devtools.ksp.symbol.impl.findParentAnnotated
import com.google.devtools.ksp.symbol.impl.findPsi
import com.google.devtools.ksp.symbol.impl.getInstanceForCurrentRound
import com.google.devtools.ksp.symbol.impl.java.*
import com.google.devtools.ksp.symbol.impl.jvmAccessFlag
import com.google.devtools.ksp.symbol.impl.kotlin.*
import com.google.devtools.ksp.symbol.impl.resolveContainingClass
import com.google.devtools.ksp.symbol.impl.synthetic.*
import com.google.devtools.ksp.visitor.CollectAnnotatedSymbolsVisitor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.impl.source.PsiClassReferenceType
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.state.JVM_SUPPRESS_WILDCARDS_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.codegen.state.JVM_WILDCARD_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.container.tryGetService
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.components.TypeUsage
import org.jetbrains.kotlin.load.java.descriptors.JavaForKotlinOverridePropertyDescriptor
import org.jetbrains.kotlin.load.java.lazy.JavaResolverComponents
import org.jetbrains.kotlin.load.java.lazy.LazyJavaResolverContext
import org.jetbrains.kotlin.load.java.lazy.ModuleClassResolver
import org.jetbrains.kotlin.load.java.lazy.TypeParameterResolver
import org.jetbrains.kotlin.load.java.lazy.childForClassOrPackage
import org.jetbrains.kotlin.load.java.lazy.childForMethod
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaTypeParameterDescriptor
import org.jetbrains.kotlin.load.java.lazy.types.JavaTypeResolver
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
import org.jetbrains.kotlin.load.java.structure.JavaClass
import org.jetbrains.kotlin.load.java.structure.classId
import org.jetbrains.kotlin.load.java.structure.impl.JavaArrayTypeImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaConstructorImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaFieldImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaMethodImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeParameterImpl
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaMethod
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaMethodBase
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.getOptimalModeForReturnType
import org.jetbrains.kotlin.load.kotlin.getOptimalModeForValueParameter
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.calls.inference.components.NewTypeSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.components.composeWith
import org.jetbrains.kotlin.resolve.calls.inference.substitute
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.constants.*
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.descriptorUtil.propertyIfAccessor
import org.jetbrains.kotlin.resolve.lazy.DeclarationScopeProvider
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.multiplatform.findActuals
import org.jetbrains.kotlin.resolve.multiplatform.findExpects
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.SimpleClassicTypeSystemContext
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.kotlin.types.typeUtil.substitute
import org.jetbrains.kotlin.types.typeUtil.supertypes
import org.jetbrains.kotlin.util.containingNonLocalDeclaration
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION
import java.io.File
import java.util.Stack
class ResolverImpl(
val module: ModuleDescriptor,
val allKSFiles: Collection<KSFile>,
val newKSFiles: Collection<KSFile>,
private val deferredSymbols: Map<SymbolProcessor, List<KSAnnotated>>,
val bindingTrace: BindingTrace,
val project: Project,
componentProvider: ComponentProvider,
val incrementalContext: IncrementalContext,
val options: KspOptions,
) : Resolver {
val psiDocumentManager = PsiDocumentManager.getInstance(project)
private val nameToKSMap: MutableMap<KSName, KSClassDeclaration>
private val javaTypeParameterMap: MutableMap<LazyJavaTypeParameterDescriptor, PsiTypeParameter> = mutableMapOf()
/**
* Checking as member of is an expensive operation, hence we cache result values in this map.
*/
private val functionAsMemberOfCache: MutableMap<Pair<KSFunctionDeclaration, KSType>, KSFunction>
private val propertyAsMemberOfCache: MutableMap<Pair<KSPropertyDeclaration, KSType>, KSType>
private val typeMapper = KotlinTypeMapper(
BindingContext.EMPTY, ClassBuilderMode.LIGHT_CLASSES,
module.name.getNonSpecialIdentifier(),
KotlinTypeMapper.LANGUAGE_VERSION_SETTINGS_DEFAULT, // TODO use proper LanguageVersionSettings
true
)
private val qualifiedExpressionResolver = QualifiedExpressionResolver(LanguageVersionSettingsImpl.DEFAULT)
private val aliasingFqNs: MutableMap<String, KSTypeAlias> = mutableMapOf()
private val aliasingNames: MutableSet<String> = mutableSetOf()
private val topDownAnalysisContext by lazy {
TopDownAnalysisContext(TopDownAnalysisMode.TopLevelDeclarations, DataFlowInfo.EMPTY, declarationScopeProvider)
}
companion object {
var instance: ResolverImpl? = null
}
var resolveSession: ResolveSession
var bodyResolver: BodyResolver
var constantExpressionEvaluator: ConstantExpressionEvaluator
var declarationScopeProvider: DeclarationScopeProvider
lateinit var moduleClassResolver: ModuleClassResolver
lateinit var javaTypeResolver: JavaTypeResolver
lateinit var lazyJavaResolverContext: LazyJavaResolverContext
init {
resolveSession = componentProvider.get()
bodyResolver = componentProvider.get()
declarationScopeProvider = componentProvider.get()
constantExpressionEvaluator = componentProvider.get()
componentProvider.tryGetService(JavaResolverComponents::class.java)?.let {
lazyJavaResolverContext = LazyJavaResolverContext(it, TypeParameterResolver.EMPTY) { null }
javaTypeResolver = lazyJavaResolverContext.typeResolver
moduleClassResolver = lazyJavaResolverContext.components.moduleClassResolver
}
instance = this
nameToKSMap = mutableMapOf()
functionAsMemberOfCache = mutableMapOf()
propertyAsMemberOfCache = mutableMapOf()
val visitor = object : KSVisitorVoid() {
override fun visitFile(file: KSFile, data: Unit) {
file.declarations.forEach { it.accept(this, data) }
// TODO: evaluate with benchmarks: cost of getContainingFile v.s. name collision
// Import aliases are file-scoped. `aliasingNamesByFile` could be faster
(file as? KSFileImpl)?.file?.importDirectives?.forEach {
it.aliasName?.let { aliasingNames.add(it) }
}
}
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
val qualifiedName = classDeclaration.qualifiedName
if (qualifiedName != null) {
nameToKSMap[qualifiedName] = classDeclaration
}
classDeclaration.declarations.forEach { it.accept(this, data) }
}
override fun visitTypeAlias(typeAlias: KSTypeAlias, data: Unit) {
typeAlias.qualifiedName?.asString()?.let { fqn ->
aliasingFqNs[fqn] = typeAlias
aliasingNames.add(fqn.substringAfterLast('.'))
}
}
}
// FIXME: reuse results from previous rounds and only loop through newKSFiles.
allKSFiles.forEach { it.accept(visitor, Unit) }
}
// Mitigation for processors with memory leaks
// https://github.com/google/ksp/issues/1063
fun tearDown() {
instance = null
}
override fun getNewFiles(): Sequence<KSFile> {
return newKSFiles.asSequence()
}
override fun getAllFiles(): Sequence<KSFile> {
return allKSFiles.asSequence()
}
override fun getClassDeclarationByName(name: KSName): KSClassDeclaration? {
nameToKSMap[name]?.let { return it }
return module.resolveClassByFqName(FqName(name.asString()), NoLookupLocation.WHEN_FIND_BY_FQNAME)
?.let {
val psi = it.findPsi()
if (psi != null) {
when (psi) {
is KtClassOrObject -> KSClassDeclarationImpl.getCached(psi)
is PsiClass -> KSClassDeclarationJavaImpl.getCached(psi)
else -> throw IllegalStateException("unexpected psi: ${psi.javaClass}")
}
} else {
KSClassDeclarationDescriptorImpl.getCached(it)
}
}
}
override fun getFunctionDeclarationsByName(
name: KSName,
includeTopLevel: Boolean,
): Sequence<KSFunctionDeclaration> {
val qualifier = name.getQualifier()
val functionName = name.getShortName()
val nonTopLevelResult = this.getClassDeclarationByName(qualifier)?.getDeclaredFunctions()
?.filter { it.simpleName.asString() == functionName }?.asSequence() ?: emptySequence()
return if (!includeTopLevel) nonTopLevelResult else {
nonTopLevelResult.plus(
module.getPackage(FqName(qualifier))
.memberScope.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) {
it.asString() == functionName
}
.filterIsInstance<MemberDescriptor>().mapNotNull { it.toKSDeclaration() as? KSFunctionDeclaration }
)
}
}
override fun getPropertyDeclarationByName(name: KSName, includeTopLevel: Boolean): KSPropertyDeclaration? {
val qualifier = name.getQualifier()
val propertyName = name.getShortName()
val nonTopLevelResult = this.getClassDeclarationByName(qualifier)?.getDeclaredProperties()
?.singleOrNull { it.simpleName.asString() == propertyName }
return if (!includeTopLevel) nonTopLevelResult else {
val topLevelResult = (
module.getPackage(FqName(qualifier))
.memberScope.getContributedDescriptors(DescriptorKindFilter.VARIABLES) {
it.asString() == propertyName
}
.also {
if (it.size > 1) {
throw IllegalStateException("Found multiple properties with same qualified name")
}
}
.singleOrNull() as? MemberDescriptor
)?.toKSDeclaration() as? KSPropertyDeclaration
if (topLevelResult != null && nonTopLevelResult != null) {
throw IllegalStateException("Found multiple properties with same qualified name")
}
nonTopLevelResult ?: topLevelResult
}
}
internal fun checkAnnotation(annotation: KSAnnotation, ksName: KSName, shortName: String): Boolean {
val annotationType = annotation.annotationType
val referencedName = (annotationType.element as? KSClassifierReference)?.referencedName()
val simpleName = referencedName?.substringAfterLast('.')
return (simpleName == shortName || simpleName in aliasingNames) &&
annotationType.resolveToUnderlying().declaration.qualifiedName == ksName
}
override fun getSymbolsWithAnnotation(annotationName: String, inDepth: Boolean): Sequence<KSAnnotated> {
// If annotationName is a typealias, resolve to underlying type.
val realAnnotationName =
aliasingFqNs[annotationName]?.type?.resolveToUnderlying()?.declaration?.qualifiedName?.asString()
?: annotationName
val ksName = KSNameImpl.getCached(realAnnotationName)
val shortName = ksName.getShortName()
fun checkAnnotated(annotated: KSAnnotated): Boolean {
return annotated.annotations.any {
checkAnnotation(it, ksName, shortName)
}
}
val allAnnotated = if (inDepth) newAnnotatedSymbolsWithLocals else newAnnotatedSymbols
return allAnnotated.asSequence().filter(::checkAnnotated)
}
private fun collectAnnotatedSymbols(inDepth: Boolean): Collection<KSAnnotated> {
val visitor = CollectAnnotatedSymbolsVisitor(inDepth)
for (file in newKSFiles) {
file.accept(visitor, Unit)
}
return visitor.symbols
}
private val deferredSymbolsUpdated: Collection<KSAnnotated> by lazy {
deferredSymbols.values.flatten().mapNotNull { it.getInstanceForCurrentRound() }
}
private val newAnnotatedSymbols: Collection<KSAnnotated> by lazy {
collectAnnotatedSymbols(false) + deferredSymbolsUpdated
}
private val newAnnotatedSymbolsWithLocals: Collection<KSAnnotated> by lazy {
collectAnnotatedSymbols(true) + deferredSymbolsUpdated
}
override fun getKSNameFromString(name: String): KSName {
return KSNameImpl.getCached(name)
}
override fun createKSTypeReferenceFromKSType(type: KSType): KSTypeReference {
return KSTypeReferenceSyntheticImpl.getCached(type, null)
}
@KspExperimental
override fun mapToJvmSignature(declaration: KSDeclaration): String? = mapToJvmSignatureInternal(declaration)
internal fun mapToJvmSignatureInternal(declaration: KSDeclaration): String? = when (declaration) {
is KSClassDeclaration -> resolveClassDeclaration(declaration)?.let { typeMapper.mapType(it).descriptor }
is KSFunctionDeclaration -> resolveFunctionDeclaration(declaration)?.let {
when (it) {
is FunctionDescriptor -> typeMapper.mapAsmMethod(it).descriptor
is PropertyDescriptor -> typeMapper.mapFieldSignature(it.type, it) ?: typeMapper.mapType(it).descriptor
else -> throw IllegalStateException("Unexpected descriptor type for declaration: $declaration")
}
}
is KSPropertyDeclaration -> resolvePropertyDeclaration(declaration)?.let {
typeMapper.mapFieldSignature(it.type, it) ?: typeMapper.mapType(it).descriptor
}
else -> null
}
override fun overrides(overrider: KSDeclaration, overridee: KSDeclaration): Boolean {
fun resolveForOverride(declaration: KSDeclaration): DeclarationDescriptor? {
return when (declaration) {
is KSPropertyDeclaration -> resolvePropertyDeclaration(declaration)
is KSFunctionDeclarationJavaImpl -> resolveJavaDeclaration(declaration.psi)
is KSFunctionDeclaration -> resolveFunctionDeclaration(declaration)
else -> null
}
}
if (!overridee.isOpen())
return false
if (!overridee.isVisibleFrom(overrider))
return false
if (!(
overridee is KSFunctionDeclaration || overrider is KSFunctionDeclaration ||
(overridee is KSPropertyDeclaration && overrider is KSPropertyDeclaration)
)
)
return false
if (overrider is KSPropertyDeclarationJavaImpl)
return false
val superDescriptor = resolveForOverride(overridee) as? CallableMemberDescriptor ?: return false
val subDescriptor = resolveForOverride(overrider) as? CallableMemberDescriptor ?: return false
val subClassDescriptor = overrider.closestClassDeclaration()?.let {
resolveClassDeclaration(it)
} ?: return false
val superClassDescriptor = overridee.closestClassDeclaration()?.let {
resolveClassDeclaration(it)
} ?: return false
incrementalContext.recordLookupWithSupertypes(subClassDescriptor.defaultType)
val typeOverride = subClassDescriptor.getAllSuperClassifiers()
.filter { it != subClassDescriptor } // exclude subclass itself as it cannot override its own methods
.any {
it == superClassDescriptor
}
if (!typeOverride) return false
incrementalContext.recordLookupForDeclaration(overrider)
incrementalContext.recordLookupForDeclaration(overridee)
return OverridingUtil.overrides(subDescriptor, superDescriptor, true, true)
}
// check if the candidate is overridden from the original declaration.
private fun isOriginal(original: KSDeclaration, candidate: KSDeclaration): Boolean {
incrementalContext.recordLookupForDeclaration(original)
incrementalContext.recordLookupForDeclaration(candidate)
val originalDescriptor = when (original) {
is KSPropertyDeclaration -> resolvePropertyDeclaration(original)
is KSFunctionDeclaration ->
(resolveFunctionDeclaration(original) as? FunctionDescriptor)?.propertyIfAccessor
else -> return false
}
val candidateDescriptors = when (candidate) {
is KSPropertyDeclaration -> resolvePropertyDeclaration(candidate)?.overriddenDescriptors
is KSFunctionDeclaration -> resolveFunctionDeclaration(candidate)?.overriddenDescriptors
else -> return false
}
return candidateDescriptors?.any { it == originalDescriptor } ?: false
}
override fun overrides(
overrider: KSDeclaration,
overridee: KSDeclaration,
containingClass: KSClassDeclaration
): Boolean {
incrementalContext.recordLookupForDeclaration(containingClass)
return when (overrider) {
is KSPropertyDeclaration -> containingClass.getAllProperties().singleOrNull {
it.simpleName.asString() == overrider.simpleName.asString() && isOriginal(overrider, it)
}?.let { overrides(it, overridee) } ?: false
is KSFunctionDeclaration -> {
val candidates = containingClass.getAllFunctions().filter {
it.simpleName.asString() == overridee.simpleName.asString()
}
if (overrider.simpleName.asString().startsWith("get") ||
overrider.simpleName.asString().startsWith("set")
) {
candidates.plus(
containingClass.getAllProperties().filter {
val overriderName = overrider.simpleName.asString().substring(3)
.replaceFirstChar { it.lowercase() }
it.simpleName.asString() == overriderName ||
it.simpleName.asString().replaceFirstChar { it.lowercase() } == overriderName
}
// TODO: It is currently not possible to do the overridden descriptor optimization for java overrides.
).any { overrides(it, overridee) }
} else {
candidates.singleOrNull { isOriginal(overrider, it) }?.let { overrides(it, overridee) } ?: false
}
}
else -> false
}
}
fun evaluateConstant(expression: KtExpression?, expectedType: KotlinType): ConstantValue<*>? {
return expression?.let {
if (it is KtClassLiteralExpression && it.receiverExpression != null) {
val parent = KtStubbedPsiUtil.getPsiOrStubParent(it, KtPrimaryConstructor::class.java, false)
val scope = resolveSession.declarationScopeProvider.getResolutionScopeForDeclaration(parent!!)
val result = qualifiedExpressionResolver
.resolveDescriptorForDoubleColonLHS(it.receiverExpression!!, scope, bindingTrace, false)
val classifier = result.classifierDescriptor ?: return null
val typeResolutionContext = TypeResolutionContext(scope, bindingTrace, true, true, false)
val possiblyBareType = resolveSession.typeResolver
.resolveTypeForClassifier(typeResolutionContext, classifier, result, it, Annotations.EMPTY)
var actualType = if (possiblyBareType.isBare)
possiblyBareType.bareTypeConstructor.declarationDescriptor!!.defaultType
else possiblyBareType.actualType
var arrayDimension = 0
while (KotlinBuiltIns.isArray(actualType)) {
actualType = actualType.arguments.single().type
arrayDimension += 1
}
KClassValue(actualType.constructor.declarationDescriptor.classId!!, arrayDimension)
} else {
constantExpressionEvaluator.evaluateExpression(it, bindingTrace)?.toConstantValue(expectedType) ?: run {
val parent = KtStubbedPsiUtil
.getPsiOrStubParent(expression, KtPrimaryConstructor::class.java, false)
val scope = resolveSession.declarationScopeProvider.getResolutionScopeForDeclaration(parent!!)
qualifiedExpressionResolver
.resolvePackageHeader(expression.containingKtFile.packageDirective!!, module, bindingTrace)
bodyResolver.resolveConstructorParameterDefaultValues(
topDownAnalysisContext.outerDataFlowInfo, bindingTrace,
parent, (scope.ownerDescriptor as ClassDescriptor).constructors.first(), scope,
resolveSession.inferenceSession
)
constantExpressionEvaluator.evaluateExpression(it, bindingTrace)?.toConstantValue(expectedType)
}
}
}
}
fun resolveDeclaration(declaration: KtDeclaration): DeclarationDescriptor? {
return if (KtPsiUtil.isLocal(declaration)) {
resolveDeclarationForLocal(declaration)
bindingTrace.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration)
} else {
resolveSession.resolveToDescriptor(declaration)
}
}
// TODO: Resolve Java variables is not supported by this function. Not needed currently.
fun resolveJavaDeclaration(psi: PsiElement): DeclarationDescriptor? {
return when (psi) {
is PsiClass -> moduleClassResolver.resolveClass(
JavaClassImpl(psi).apply { workaroundForNested(lazyJavaResolverContext) }
)
is PsiMethod -> {
// TODO: get rid of hardcoded check if possible.
val property = if (psi.name.startsWith("set") || psi.name.startsWith("get")) {
moduleClassResolver
.resolveContainingClass(psi)
?.findEnclosedDescriptor(
kindFilter = DescriptorKindFilter.CALLABLES
) {
(it as? PropertyDescriptor)?.getter?.findPsi() == psi ||
(it as? PropertyDescriptor)?.setter?.findPsi() == psi
}
} else null
property ?: moduleClassResolver
.resolveContainingClass(psi)?.let { containingClass ->
val filter = if (psi is SyntheticElement) {
{ declaration: DeclarationDescriptor -> declaration.name.asString() == psi.name }
} else {
{ declaration: DeclarationDescriptor -> declaration.findPsi() == psi }
}
containingClass.findEnclosedDescriptor(
kindFilter = DescriptorKindFilter.FUNCTIONS,
filter = filter
)
}
}
is PsiField -> {
moduleClassResolver
.resolveClass(
JavaFieldImpl(psi).containingClass.apply { workaroundForNested(lazyJavaResolverContext) }
)
?.findEnclosedDescriptor(
kindFilter = DescriptorKindFilter.VARIABLES,
filter = { it.findPsi() == psi }
)
}
else -> throw IllegalStateException("unhandled psi element kind: ${psi.javaClass}")
}
}
fun resolveClassDeclaration(classDeclaration: KSClassDeclaration): ClassDescriptor? {
return when (classDeclaration) {
is KSClassDeclarationImpl -> resolveDeclaration(classDeclaration.ktClassOrObject)
is KSClassDeclarationDescriptorImpl -> classDeclaration.descriptor
is KSClassDeclarationJavaImpl -> resolveJavaDeclaration(classDeclaration.psi)
else -> throw IllegalStateException("unexpected class: ${classDeclaration.javaClass}")
} as ClassDescriptor?
}
fun resolveFunctionDeclaration(function: KSFunctionDeclaration): CallableDescriptor? {
return when (function) {
is KSFunctionDeclarationImpl -> resolveDeclaration(function.ktFunction)
is KSFunctionDeclarationDescriptorImpl -> function.descriptor
is KSFunctionDeclarationJavaImpl -> {
val descriptor = resolveJavaDeclaration(function.psi)
if (descriptor is JavaForKotlinOverridePropertyDescriptor) {
if (function.simpleName.asString().startsWith("set")) {
descriptor.setter
} else {
descriptor.getter
}
} else {
descriptor
}
}
is KSConstructorSyntheticImpl -> {
// we might create synthetic constructor when it is not declared in code
// it is either for kotlin, where we can use primary constructor, or for java
// where we can use the only available constructor
val resolved = resolveClassDeclaration(function.ksClassDeclaration)
resolved?.unsubstitutedPrimaryConstructor ?: resolved?.constructors?.singleOrNull()
}
else -> throw IllegalStateException("unexpected class: ${function.javaClass}")
} as? CallableDescriptor
}
fun resolvePropertyDeclaration(property: KSPropertyDeclaration): PropertyDescriptor? {
return when (property) {
is KSPropertyDeclarationImpl -> resolveDeclaration(property.ktProperty)
is KSPropertyDeclarationParameterImpl -> resolveDeclaration(property.ktParameter)
is KSPropertyDeclarationDescriptorImpl -> property.descriptor
is KSPropertyDeclarationJavaImpl -> resolveJavaDeclaration(property.psi)
else -> throw IllegalStateException("unexpected class: ${property.javaClass}")
} as PropertyDescriptor?
}
fun resolvePropertyAccessorDeclaration(accessor: KSPropertyAccessor): PropertyAccessorDescriptor? {
return when (accessor) {
is KSPropertyAccessorDescriptorImpl -> accessor.descriptor
is KSPropertyAccessorImpl -> resolveDeclaration(accessor.ktPropertyAccessor)
is KSPropertySetterSyntheticImpl -> resolvePropertyDeclaration(accessor.receiver)?.setter
is KSPropertyGetterSyntheticImpl -> resolvePropertyDeclaration(accessor.receiver)?.getter
else -> throw IllegalStateException("unexpected class: ${accessor.javaClass}")
} as PropertyAccessorDescriptor?
}
fun resolveJavaType(psi: PsiType, parentTypeReference: KSTypeReference? = null): KotlinType {
incrementalContext.recordLookup(psi)
val javaType = JavaTypeImpl.create(psi)
var parent: KSNode? = parentTypeReference
val stack = Stack<KSNode>()
while (parent != null) {
if (parent is KSFunctionDeclarationJavaImpl || parent is KSClassDeclarationJavaImpl) {
stack.push(parent)
}
parent = parent.parent
}
// Construct resolver context for the PsiType
var resolverContext = lazyJavaResolverContext
for (e in stack) {
when (e) {
is KSFunctionDeclarationJavaImpl -> {
// Non-physical methods have no interesting scope and may have no containing class
if (!e.psi.isPhysical || e.psi.containingClass == null)
continue
resolverContext = resolverContext
.childForMethod(
resolveJavaDeclaration(e.psi)!!,
if (e.psi.isConstructor) JavaConstructorImpl(e.psi) else JavaMethodImpl(e.psi)
)
}
is KSClassDeclarationJavaImpl -> {
resolverContext = resolverContext
.childForClassOrPackage(resolveJavaDeclaration(e.psi) as ClassDescriptor, JavaClassImpl(e.psi))
}
}
}
return if (javaType is JavaArrayTypeImpl)
resolverContext
.typeResolver.transformArrayType(javaType, TypeUsage.COMMON.toAttributes(), psi is PsiEllipsisType)
else
resolverContext.typeResolver.transformJavaType(javaType, TypeUsage.COMMON.toAttributes())
}
/*
* Don't map Java types in annotation parameters
*
* Users may specify Java types explicitly by instances of `Class<T>`.
* The situation is similar to `getClassDeclarationByName` where we have
* decided to keep those Java types not mapped.
*
* It would be troublesome if users try to use reflection on types that
* were mapped to Kotlin builtins, becuase some of those builtins don't
* even exist in classpath.
*
* Therefore, ResolverImpl.resolveJavaType cannot be used.
*/
fun resolveJavaTypeInAnnotations(psiType: PsiType): KSType = if (options.mapAnnotationArgumentsInJava) {
getKSTypeCached(resolveJavaType(psiType))
} else {
when (psiType) {
is PsiPrimitiveType -> {
getClassDeclarationByName(psiType.boxedTypeName!!)!!.asStarProjectedType()
}
is PsiArrayType -> {
val componentType = resolveJavaTypeInAnnotations(psiType.componentType)
val componentTypeRef = createKSTypeReferenceFromKSType(componentType)
val typeArgs = listOf(getTypeArgument(componentTypeRef, Variance.INVARIANT))
builtIns.arrayType.replace(typeArgs)
}
else -> {
getClassDeclarationByName(psiType.canonicalText)?.asStarProjectedType() ?: KSErrorType
}
}
}
fun KotlinType.expandNonRecursively(): KotlinType =
(constructor.declarationDescriptor as? TypeAliasDescriptor)?.expandedType?.withAbbreviation(this as SimpleType)
?: this
fun TypeProjection.expand(): TypeProjection {
val expandedType = type.expand()
return if (expandedType == type) this else substitute { expandedType }
}
// TODO: Is this the most efficient way?
fun KotlinType.expand(): KotlinType =
replace(arguments.map { it.expand() }).expandNonRecursively()
fun KtTypeReference.lookup(): KotlinType? =
bindingTrace.get(BindingContext.ABBREVIATED_TYPE, this)?.expand() ?: bindingTrace.get(BindingContext.TYPE, this)
fun resolveUserType(type: KSTypeReference): KSType {
when (type) {
is KSTypeReferenceImpl -> {
val typeReference = type.ktTypeReference
typeReference.lookup()?.let {
return getKSTypeCached(it, type.element.typeArguments, type.annotations)
}
KtStubbedPsiUtil.getContainingDeclaration(typeReference)?.let { containingDeclaration ->
resolveDeclaration(containingDeclaration)?.let {
// TODO: only resolve relevant branch.
ForceResolveUtil.forceResolveAllContents(it)
}
// TODO: Fix resolution look up to avoid fallback to file scope.
typeReference.lookup()?.let {
return getKSTypeCached(it, type.element.typeArguments, type.annotations)
}
}
val scope = resolveSession.fileScopeProvider.getFileResolutionScope(typeReference.containingKtFile)
return resolveSession.typeResolver.resolveType(scope, typeReference, bindingTrace, false).let {
getKSTypeCached(it, type.element.typeArguments, type.annotations)
}
}
is KSTypeReferenceDescriptorImpl -> {
return getKSTypeCached(type.kotlinType)
}
is KSTypeReferenceJavaImpl -> {
val psi = (type.psi as? PsiClassReferenceType)?.resolve()
if (psi is PsiTypeParameter) {
(type.psi as PsiClassReferenceType).typeArguments().forEach {
if (it is PsiType) {
incrementalContext.recordLookup(it)
}
}
val containingDeclaration = if (psi.owner is PsiClass) {
moduleClassResolver.resolveClass(
JavaClassImpl(psi.owner as PsiClass).apply { workaroundForNested(lazyJavaResolverContext) }
)
} else {
val owner = psi.owner
check(owner is PsiMethod) {
"unexpected owner type: $owner / ${owner?.javaClass}"
}
moduleClassResolver.resolveContainingClass(owner)
?.findEnclosedDescriptor(
kindFilter = DescriptorKindFilter.FUNCTIONS,
filter = { it.findPsi() == owner }
) as FunctionDescriptor
} as DeclarationDescriptor
val typeParameterDescriptor = LazyJavaTypeParameterDescriptor(
lazyJavaResolverContext,
JavaTypeParameterImpl(psi),
psi.index,
containingDeclaration
)
javaTypeParameterMap[typeParameterDescriptor] = psi
}
return getKSTypeCached(resolveJavaType(type.psi, type), type.element.typeArguments, type.annotations)
}
else -> throw IllegalStateException("Unable to resolve type for $type, $ExceptionMessage")
}
}
fun findDeclaration(kotlinType: KotlinType): KSDeclaration {
val descriptor = kotlinType.constructor.declarationDescriptor
val psi = descriptor?.findPsi()
return if (psi != null) {
when (psi) {
is KtClassOrObject -> KSClassDeclarationImpl.getCached(psi)
is PsiClass -> KSClassDeclarationJavaImpl.getCached(psi)
is KtTypeAlias -> KSTypeAliasImpl.getCached(psi)
is KtTypeParameter -> KSTypeParameterImpl.getCached(psi)
is PsiEnumConstant -> KSClassDeclarationJavaEnumEntryImpl.getCached(psi)
else -> throw IllegalStateException("Unexpected psi type: ${psi.javaClass}, $ExceptionMessage")
}
} else {
when (descriptor) {
is ClassDescriptor -> KSClassDeclarationDescriptorImpl.getCached(descriptor)
// LazyJavaTypeParameterDescriptor has `source` overridden to `NO_SOURCE`, therefore
// need to look up psi within KSP.
is TypeParameterDescriptor -> if (descriptor in javaTypeParameterMap) {
KSTypeParameterJavaImpl.getCached(javaTypeParameterMap[descriptor]!!)
} else {
KSTypeParameterDescriptorImpl.getCached(descriptor)
}
is TypeAliasDescriptor -> KSTypeAliasDescriptorImpl.getCached(descriptor)
null -> throw IllegalStateException("Failed to resolve descriptor for $kotlinType")
else -> throw IllegalStateException(
"Unexpected descriptor type: ${descriptor.javaClass}, $ExceptionMessage"
)
}
}
}
// Finds closest non-local scope.
fun KtElement.findLexicalScope(): LexicalScope {
return containingNonLocalDeclaration()?.let {
resolveSession.declarationScopeProvider.getResolutionScopeForDeclaration(it)
} ?: resolveSession.fileScopeProvider.getFileResolutionScope(this.containingKtFile)
}
fun resolveAnnotationEntry(ktAnnotationEntry: KtAnnotationEntry): AnnotationDescriptor? {
bindingTrace.get(BindingContext.ANNOTATION, ktAnnotationEntry)?.let { return it }
KtStubbedPsiUtil.getContainingDeclaration(ktAnnotationEntry)?.let { containingDeclaration ->
if (KtPsiUtil.isLocal(containingDeclaration)) {
resolveDeclarationForLocal(containingDeclaration)
} else {
resolveSession.resolveToDescriptor(containingDeclaration).annotations.forEach {}
}
} ?: ktAnnotationEntry.containingKtFile.let {
resolveSession.getFileAnnotations(it).forEach {}
}
return bindingTrace.get(BindingContext.ANNOTATION, ktAnnotationEntry)
}
fun resolveDeclarationForLocal(localDeclaration: KtDeclaration) {
var declaration = KtStubbedPsiUtil.getContainingDeclaration(localDeclaration) ?: return
while (KtPsiUtil.isLocal(declaration))
declaration = KtStubbedPsiUtil.getContainingDeclaration(declaration)!!
val containingFD = resolveSession.resolveToDescriptor(declaration).also {
ForceResolveUtil.forceResolveAllContents(it)
}
if (declaration is KtNamedFunction) {
val dataFlowInfo = DataFlowInfo.EMPTY
val scope = resolveSession.declarationScopeProvider.getResolutionScopeForDeclaration(declaration)
bodyResolver.resolveFunctionBody(
dataFlowInfo,
bindingTrace,
declaration,
containingFD as FunctionDescriptor,
scope,
null
)
}
}
@KspExperimental
override fun getJvmName(accessor: KSPropertyAccessor): String? {
val descriptor = resolvePropertyAccessorDeclaration(accessor)
return descriptor?.let {
// KotlinTypeMapper.mapSignature always uses OwnerKind.IMPLEMENTATION
typeMapper.mapFunctionName(descriptor, OwnerKind.IMPLEMENTATION)
}
}
@KspExperimental
override fun getJvmName(declaration: KSFunctionDeclaration): String? {
// function names might be mangled if they receive inline class parameters or they are internal
val descriptor = resolveFunctionDeclaration(declaration)
return (descriptor as? FunctionDescriptor)?.let {
// KotlinTypeMapper.mapSignature always uses OwnerKind.IMPLEMENTATION
typeMapper.mapFunctionName(it, OwnerKind.IMPLEMENTATION)
}
}
@KspExperimental
override fun getOwnerJvmClassName(declaration: KSPropertyDeclaration): String? {
val descriptor = resolvePropertyDeclaration(declaration) ?: return null
return getJvmOwnerQualifiedName(descriptor)
}
@KspExperimental
override fun getOwnerJvmClassName(declaration: KSFunctionDeclaration): String? {
val descriptor = resolveFunctionDeclaration(declaration) ?: return null
return getJvmOwnerQualifiedName(descriptor)
}
private fun getJvmOwnerQualifiedName(descriptor: DeclarationDescriptor): String? {
return try {
typeMapper.mapImplementationOwner(descriptor).className
} catch (unsupported: UnsupportedOperationException) {
null
}
}
private fun extractThrowsFromClassFile(
virtualFileContent: ByteArray,
jvmDesc: String?,
simpleName: String?
): Sequence<KSType> {
val exceptionNames = mutableListOf<String>()
ClassReader(virtualFileContent).accept(
object : ClassVisitor(API_VERSION) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?,
): MethodVisitor {
if (name == simpleName && jvmDesc == descriptor) {
exceptions?.toList()?.let { exceptionNames.addAll(it) }
}
return object : MethodVisitor(API_VERSION) {
}
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)
return exceptionNames.mapNotNull {
this@ResolverImpl.getClassDeclarationByName(it.replace("/", "."))?.asStarProjectedType()
}.asSequence()
}
@SuppressWarnings("UNCHECKED_CAST")
private fun extractThrowsAnnotation(annotated: KSAnnotated): Sequence<KSType> {
return annotated.annotations
.singleOrNull {
it.shortName.asString() == "Throws" &&
it.annotationType.resolve().declaration.qualifiedName?.asString() == "kotlin.Throws"
}?.arguments
?.singleOrNull()
?.let { it.value as? ArrayList<KSType> }
?.asSequence() ?: emptySequence()
}
// TODO: refactor and reuse BinaryClassInfoCache
@KspExperimental
override fun getJvmCheckedException(function: KSFunctionDeclaration): Sequence<KSType> {
return when (function.origin) {
Origin.JAVA -> {
val psi = (function as KSFunctionDeclarationJavaImpl).psi
psi.throwsList.referencedTypes.asSequence().map { getKSTypeCached(resolveJavaType(it)) }
}
Origin.KOTLIN -> {
extractThrowsAnnotation(function)
}
Origin.KOTLIN_LIB, Origin.JAVA_LIB -> {
val descriptor = (function as KSFunctionDeclarationDescriptorImpl).descriptor
val jvmDesc = this.mapToJvmSignature(function)
val virtualFileContent = if (function.origin == Origin.KOTLIN_LIB) {
(descriptor.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass)?.file
?.contentsToByteArray()
} else {
(
((descriptor.source as? JavaSourceElement)?.javaElement as? BinaryJavaMethodBase)
?.containingClass as? BinaryJavaClass
)?.virtualFile?.contentsToByteArray()
}
if (virtualFileContent == null) {
return emptySequence()
}
extractThrowsFromClassFile(virtualFileContent, jvmDesc, function.simpleName.asString())
}
else -> emptySequence()
}
}
@KspExperimental
override fun getJvmCheckedException(accessor: KSPropertyAccessor): Sequence<KSType> {
return when (accessor.origin) {
Origin.KOTLIN, Origin.SYNTHETIC -> {
extractThrowsAnnotation(accessor)
}
Origin.KOTLIN_LIB -> {
val descriptor = (accessor as KSPropertyAccessorDescriptorImpl).descriptor
val jvmDesc = typeMapper.mapAsmMethod(descriptor).descriptor
val virtualFileContent = if (accessor.origin == Origin.KOTLIN_LIB) {
(descriptor.correspondingProperty.getContainingKotlinJvmBinaryClass() as? VirtualFileKotlinClass)
?.file?.contentsToByteArray()
} else {
(
((descriptor.source as? JavaSourceElement)?.javaElement as? BinaryJavaMethod)?.containingClass
as? BinaryJavaClass
)?.virtualFile?.contentsToByteArray()
}
if (virtualFileContent == null) {
return emptySequence()
}
extractThrowsFromClassFile(virtualFileContent, jvmDesc, getJvmName(accessor))
}
else -> emptySequence()
}
}
private val javaPackageToClassMap: Map<String, List<KSDeclaration>> by lazy {
val packageToClassMapping = mutableMapOf<String, List<KSDeclaration>>()
allKSFiles
.filter { file ->
file.origin == Origin.JAVA &&
options.javaSourceRoots.any { root ->
file.filePath.startsWith(root.absolutePath) &&
file.filePath.substringAfter(root.absolutePath)
.dropLastWhile { c -> c != File.separatorChar }.dropLast(1).drop(1)
.replace(File.separatorChar, '.') == file.packageName.asString()
}
}
.forEach {
packageToClassMapping.put(
it.packageName.asString(),
packageToClassMapping.getOrDefault(it.packageName.asString(), emptyList())
.plus(
it.declarations.filterNot {
it.containingFile?.fileName?.split(".")?.first() == it.simpleName.asString()
}
)
)
}
packageToClassMapping
}
@KspExperimental
override fun getDeclarationsFromPackage(packageName: String): Sequence<KSDeclaration> {
val noPackageFilter = DescriptorKindFilter.ALL.withoutKinds(DescriptorKindFilter.PACKAGES_MASK)
return module.getPackage(FqName(packageName))
.memberScope.getContributedDescriptors(noPackageFilter)
.asSequence()
.mapNotNull { (it as? MemberDescriptor)?.toKSDeclaration() }
.plus(javaPackageToClassMap.getOrDefault(packageName, emptyList()).asSequence())
}
override fun getTypeArgument(typeRef: KSTypeReference, variance: Variance): KSTypeArgument {
return KSTypeArgumentLiteImpl.getCached(typeRef, variance)
}
internal fun asMemberOf(
property: KSPropertyDeclaration,
containing: KSType,
): KSType {
val key = property to containing
return propertyAsMemberOfCache.getOrPut(key) {
computeAsMemberOf(property, containing)
}
}
private fun computeAsMemberOf(
property: KSPropertyDeclaration,
containing: KSType,
): KSType {
val propertyDeclaredIn = property.closestClassDeclaration()
?: throw IllegalArgumentException(
"Cannot call asMemberOf with a property that is " +
"not declared in a class or an interface"
)
val declaration = resolvePropertyDeclaration(property)
if (declaration != null && containing is KSTypeImpl && !containing.isError) {
incrementalContext.recordLookupWithSupertypes(containing.kotlinType)
incrementalContext.recordLookupForDeclaration(property)
if (!containing.kotlinType.isSubtypeOf(propertyDeclaredIn)) {
throw IllegalArgumentException(
"$containing is not a sub type of the class/interface that contains `$property` " +
"($propertyDeclaredIn)"
)
}
val typeSubstitutor = containing.kotlinType.createTypeSubstitutor()
val substituted = declaration.substitute(typeSubstitutor) as? ValueDescriptor
substituted?.let {
return getKSTypeCached(substituted.type)
}
}
// if substitution fails, fallback to the type from the property
return KSErrorType
}
internal fun asMemberOf(
function: KSFunctionDeclaration,
containing: KSType,
): KSFunction {
val key = function to containing
return functionAsMemberOfCache.getOrPut(key) {
computeAsMemberOf(function, containing)
}
}
private fun computeAsMemberOf(
function: KSFunctionDeclaration,
containing: KSType,
): KSFunction {
val functionDeclaredIn = function.closestClassDeclaration()
?: throw IllegalArgumentException(
"Cannot call asMemberOf with a function that is " +
"not declared in a class or an interface"
)
val declaration = resolveFunctionDeclaration(function)
if (declaration != null && containing is KSTypeImpl && !containing.isError) {
incrementalContext.recordLookupWithSupertypes(containing.kotlinType)
incrementalContext.recordLookupForDeclaration(function)
if (!containing.kotlinType.isSubtypeOf(functionDeclaredIn)) {
throw IllegalArgumentException(
"$containing is not a sub type of the class/interface that contains " +
"`$function` ($functionDeclaredIn)"
)
}
val typeSubstitutor = containing.kotlinType.createTypeSubstitutor()
if (declaration is PropertyAccessorDescriptor) {
val substitutedProperty = (declaration.correspondingProperty).substitute(typeSubstitutor)
// TODO: Fix in upstream for property accessors: https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/PropertyAccessorDescriptorImpl.java#L122
return KSFunctionImpl(
(substitutedProperty as PropertyDescriptor).accessors.single {
it.name == declaration.name
}
)
}
val substituted = declaration.substitute(typeSubstitutor)
return KSFunctionImpl(substituted)
}
// if substitution fails, return an error function that resembles the original declaration
return KSFunctionErrorImpl(function)
}
private fun KotlinType.isSubtypeOf(declaration: KSClassDeclaration): Boolean {
val classDeclaration = resolveClassDeclaration(declaration)
if (classDeclaration == null) {
throw IllegalArgumentException(
"Cannot find the declaration for class $classDeclaration"
)
}
return constructor
.declarationDescriptor
?.getAllSuperClassifiers()
?.any { it == classDeclaration } == true
}
override val builtIns: KSBuiltIns by lazy {
val builtIns = module.builtIns
object : KSBuiltIns {
override val anyType: KSType by lazy { getKSTypeCached(builtIns.anyType) }
override val nothingType by lazy { getKSTypeCached(builtIns.nothingType) }
override val unitType: KSType by lazy { getKSTypeCached(builtIns.unitType) }
override val numberType: KSType by lazy { getKSTypeCached(builtIns.numberType) }
override val byteType: KSType by lazy { getKSTypeCached(builtIns.byteType) }
override val shortType: KSType by lazy { getKSTypeCached(builtIns.shortType) }
override val intType: KSType by lazy { getKSTypeCached(builtIns.intType) }
override val longType: KSType by lazy { getKSTypeCached(builtIns.longType) }
override val floatType: KSType by lazy { getKSTypeCached(builtIns.floatType) }
override val doubleType: KSType by lazy { getKSTypeCached(builtIns.doubleType) }
override val charType: KSType by lazy { getKSTypeCached(builtIns.charType) }
override val booleanType: KSType by lazy { getKSTypeCached(builtIns.booleanType) }
override val stringType: KSType by lazy { getKSTypeCached(builtIns.stringType) }
override val iterableType: KSType by lazy {
getKSTypeCached(builtIns.iterableType.replaceArgumentsWithStarProjections())
}
override val annotationType: KSType by lazy { getKSTypeCached(builtIns.annotationType) }
override val arrayType: KSType by lazy {
getKSTypeCached(builtIns.array.defaultType.replaceArgumentsWithStarProjections())
}
}
}
internal val mockSerializableType = module.builtIns.numberType.supertypes().singleOrNull {
it.constructor.declarationDescriptor?.name?.asString() == "Serializable"
}
internal val javaSerializableType = module.resolveClassByFqName(
FqName("java.io.Serializable"), NoLookupLocation.WHEN_FIND_BY_FQNAME
)?.defaultType
@KspExperimental
override fun mapJavaNameToKotlin(javaName: KSName): KSName? =
JavaToKotlinClassMap.mapJavaToKotlin(FqName(javaName.asString()))?.toKSName()
@KspExperimental
override fun mapKotlinNameToJava(kotlinName: KSName): KSName? =
JavaToKotlinClassMap.mapKotlinToJava(FqNameUnsafe(kotlinName.asString()))?.toKSName()
@KspExperimental
internal fun mapToJvmSignature(accessor: KSPropertyAccessor): String {
return resolvePropertyAccessorDeclaration(accessor)?.let {
typeMapper.mapAsmMethod(it).descriptor
} ?: ""
}
@KspExperimental
override fun getDeclarationsInSourceOrder(container: KSDeclarationContainer): Sequence<KSDeclaration> {
return container.declarationsInSourceOrder
}
@KspExperimental
override fun effectiveJavaModifiers(declaration: KSDeclaration): Set<Modifier> {
val modifiers = HashSet<Modifier>(declaration.modifiers.filter { it in javaModifiers })
// This is only needed by sources.
// PUBLIC, PRIVATE, PROTECTED are already handled in descriptor based impls.
fun addVisibilityModifiers() {
when {
declaration.isPublic() -> modifiers.add(Modifier.PUBLIC)
declaration.isPrivate() -> modifiers.add(Modifier.PRIVATE)
declaration.isProtected() -> modifiers.add(Modifier.PROTECTED)
}
}
when (declaration.origin) {
Origin.JAVA -> {
addVisibilityModifiers()
if (declaration is KSClassDeclaration && declaration.classKind == ClassKind.INTERFACE)
modifiers.add(Modifier.ABSTRACT)
}
Origin.KOTLIN -> {
addVisibilityModifiers()
if (!declaration.isOpen())
modifiers.add(Modifier.FINAL)
if (declaration.hasAnnotation(JVM_STATIC_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_STATIC)
if (declaration.hasAnnotation(JVM_DEFAULT_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_DEFAULT)
if (declaration.hasAnnotation(JVM_DEFAULT_WITHOUT_COMPATIBILITY_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_DEFAULT)
if (declaration.hasAnnotation(JVM_STRICTFP_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_STRICT)
if (declaration.hasAnnotation(JVM_SYNCHRONIZED_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_SYNCHRONIZED)
if (declaration.hasAnnotation(JVM_TRANSIENT_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_TRANSIENT)
if (declaration.hasAnnotation(JVM_VOLATILE_ANNOTATION_FQN))
modifiers.add(Modifier.JAVA_VOLATILE)
when (declaration) {
is KSClassDeclaration -> {
if (declaration.isCompanionObject)
modifiers.add(Modifier.JAVA_STATIC)
if (declaration.classKind == ClassKind.INTERFACE)
modifiers.add(Modifier.ABSTRACT)
}
is KSPropertyDeclaration -> {
if (declaration.isAbstract())
modifiers.add(Modifier.ABSTRACT)
}
is KSFunctionDeclaration -> {
if (declaration.isAbstract)
modifiers.add(Modifier.ABSTRACT)
}
}
}
Origin.KOTLIN_LIB, Origin.JAVA_LIB -> {
when (declaration) {
is KSPropertyDeclaration -> {
if (declaration.jvmAccessFlag and Opcodes.ACC_TRANSIENT != 0)
modifiers.add(Modifier.JAVA_TRANSIENT)
if (declaration.jvmAccessFlag and Opcodes.ACC_VOLATILE != 0)
modifiers.add(Modifier.JAVA_VOLATILE)
}
is KSFunctionDeclaration -> {
if (declaration.jvmAccessFlag and Opcodes.ACC_STRICT != 0)
modifiers.add(Modifier.JAVA_STRICT)
if (declaration.jvmAccessFlag and Opcodes.ACC_SYNCHRONIZED != 0)
modifiers.add(Modifier.JAVA_SYNCHRONIZED)
}
}
}
else -> Unit
}
return modifiers
}
private enum class RefPosition {
PARAMETER_TYPE,
RETURN_TYPE,
SUPER_TYPE
}
// Search in self and parents for the first type reference that is not part of a type argument.
private fun KSTypeReference.findOuterMostRef(): Pair<KSTypeReference, List<Int>> {
fun KSNode.findParentRef(): KSTypeReference? {
var parent = parent
while (parent != null && parent !is KSTypeReference)
parent = parent.parent
return parent as? KSTypeReference
}
val fallback = Pair<KSTypeReference, List<Int>>(this, emptyList())
val indexes = mutableListOf<Int>()
var candidate: KSTypeReference = this
// KSTypeArgument's parent can be either KSReferenceElement or KSType.
while (candidate.parent is KSTypeArgument) {
// If the parent is a KSType, it's a synthetic reference.
// Do nothing and reply on the fallback behavior.
val referenceElement = (candidate.parent!!.parent as? KSReferenceElement) ?: return fallback
indexes.add(referenceElement.typeArguments.indexOf(candidate.parent))
// In case the program isn't properly structured, fallback.
candidate = referenceElement.findParentRef() ?: return fallback
}
return Pair(candidate, indexes)
}
// TODO: Strict mode for catching unhandled cases.
private fun findRefPosition(ref: KSTypeReference): RefPosition = when (val parent = ref.parent) {
is KSCallableReference -> when (ref) {
parent.returnType -> RefPosition.RETURN_TYPE
else -> RefPosition.PARAMETER_TYPE
}
is KSFunctionDeclaration -> when (ref) {
parent.returnType -> RefPosition.RETURN_TYPE
else -> RefPosition.PARAMETER_TYPE
}
is KSPropertyGetter -> RefPosition.RETURN_TYPE
is KSPropertyDeclaration -> when (ref) {
parent.type -> RefPosition.RETURN_TYPE
else -> RefPosition.PARAMETER_TYPE
}
is KSClassDeclaration -> RefPosition.SUPER_TYPE
// is KSTypeArgument -> RefPosition.PARAMETER_TYPE
// is KSAnnotation -> RefPosition.PARAMETER_TYPE
// is KSTypeAlias -> RefPosition.PARAMETER_TYPE
// is KSValueParameter -> RefPosition.PARAMETER_TYPE
// is KSTypeParameter -> RefPosition.PARAMETER_TYPE
else -> RefPosition.PARAMETER_TYPE
}
private fun KSTypeReference.isReturnTypeOfAnnotationMethod(): Boolean {
var candidate = this.parent
while (candidate !is KSClassDeclaration && candidate != null)
candidate = candidate.parent
return (candidate as? KSClassDeclaration)?.classKind == ClassKind.ANNOTATION_CLASS
}
// Convert type arguments for Java wildcard, recursively.
private fun KotlinType.toWildcard(mode: TypeMappingMode): KotlinType? {
val parameters = constructor.parameters
val arguments = arguments
val wildcardArguments = parameters.zip(arguments).map { (parameter, argument) ->
if (!argument.isStarProjection &&
parameter.variance != argument.projectionKind &&
parameter.variance != org.jetbrains.kotlin.types.Variance.INVARIANT &&
argument.projectionKind != org.jetbrains.kotlin.types.Variance.INVARIANT
) {
// conflicting variances
// TODO: error message
return null
}
val argMode = mode.updateFromAnnotations(argument.type)
val variance = KotlinTypeMapper.getVarianceForWildcard(parameter, argument, argMode)
val genericMode = argMode.toGenericArgumentMode(
getEffectiveVariance(parameter.variance, argument.projectionKind)
)
TypeProjectionImpl(variance, argument.type.toWildcard(genericMode) ?: return null)
}
return replace(wildcardArguments)
}
private val JVM_SUPPRESS_WILDCARDS_NAME = KSNameImpl.getCached("kotlin.jvm.JvmSuppressWildcards")
private val JVM_SUPPRESS_WILDCARDS_SHORT = "JvmSuppressWildcards"
private fun KSTypeReference.findJvmSuppressWildcards(): Boolean? {
var candidate: KSNode? = this
while (candidate != null) {
if ((candidate is KSTypeReference || candidate is KSDeclaration)) {
(
(candidate as KSAnnotated).annotations.firstOrNull {
checkAnnotation(it, JVM_SUPPRESS_WILDCARDS_NAME, JVM_SUPPRESS_WILDCARDS_SHORT)
}?.arguments?.firstOrNull()?.value as? Boolean
)?.let {
// KSAnnotated.getAnnotationsByType is handy but it uses reflection.
return it
}
}
candidate = candidate.parent
}
return null
}
private fun TypeMappingMode.updateFromAnnotations(
type: KotlinType
): TypeMappingMode {
(
type.annotations.findAnnotation(JVM_SUPPRESS_WILDCARDS_ANNOTATION_FQ_NAME)
?.argumentValue("suppress")?.value as? Boolean
)?.let {
return this.suppressJvmWildcards(it)
}
if (type.annotations.hasAnnotation(JVM_WILDCARD_ANNOTATION_FQ_NAME)) {
return TypeMappingMode.createWithConstantDeclarationSiteWildcardsMode(
skipDeclarationSiteWildcards = false,
isForAnnotationParameter = isForAnnotationParameter,
fallbackMode = this,
needInlineClassWrapping = needInlineClassWrapping,
mapTypeAliases = mapTypeAliases
)
}
return this
}
private fun TypeMappingMode.suppressJvmWildcards(
suppress: Boolean
): TypeMappingMode {
return TypeMappingMode.createWithConstantDeclarationSiteWildcardsMode(
skipDeclarationSiteWildcards = suppress,
isForAnnotationParameter = isForAnnotationParameter,
needInlineClassWrapping = needInlineClassWrapping,
mapTypeAliases = mapTypeAliases
)
}
private fun TypeMappingMode.updateFromParents(
ref: KSTypeReference
): TypeMappingMode {
ref.findJvmSuppressWildcards()?.let {
return this.suppressJvmWildcards(it)
}
return this
}
// Type arguments need to be resolved recursively in a top-down manner. So we find and resolve the outer most
// reference that contains this argument. Then locate and return the argument.
@KspExperimental
override fun getJavaWildcard(reference: KSTypeReference): KSTypeReference {
// If the outer-most reference cannot be found, e.g., when this reference is nested in KSType.arguments,
// fallback to PARAMETER_TYPE effectively.
val (ref, indexes) = reference.findOuterMostRef()
val type = ref.resolve()
if (type.isError)
return reference
val position = findRefPosition(ref)
val kotlinType = (type as KSTypeImpl).kotlinType
val typeSystem = SimpleClassicTypeSystemContext
val typeMappingMode = when (position) {
RefPosition.PARAMETER_TYPE -> typeSystem.getOptimalModeForValueParameter(kotlinType)
RefPosition.RETURN_TYPE ->
typeSystem.getOptimalModeForReturnType(kotlinType, ref.isReturnTypeOfAnnotationMethod())
RefPosition.SUPER_TYPE -> TypeMappingMode.SUPER_TYPE
}.updateFromParents(ref)
val parameters = kotlinType.constructor.parameters
val arguments = kotlinType.arguments
parameters.zip(arguments).forEach { (parameter, argument) ->
if (position == RefPosition.SUPER_TYPE &&
argument.projectionKind != org.jetbrains.kotlin.types.Variance.INVARIANT
) {
// Type projection isn't allowed in immediate arguments to supertypes.
// TODO: error message
return KSTypeReferenceSyntheticImpl.getCached(KSErrorType, null)
}
}
val wildcardType = kotlinType.toWildcard(typeMappingMode)?.let {
var candidate: KotlinType = it
for (i in indexes.reversed()) {
candidate = candidate.arguments[i].type
}
getKSTypeCached(candidate)
} ?: KSErrorType
return KSTypeReferenceSyntheticImpl.getCached(wildcardType, null)
}
@KspExperimental
override fun isJavaRawType(type: KSType): Boolean {
return type is KSTypeImpl && type.kotlinType.unwrap() is RawType
}
private val psiJavaFiles = allKSFiles.filterIsInstance<KSFileJavaImpl>().map {
Pair(it.psi.virtualFile.path, it.psi)
}.toMap()
internal fun findPsiJavaFile(path: String): PsiFile? = psiJavaFiles.get(path)
}
// TODO: cross module resolution
fun DeclarationDescriptor.findExpectsInKSDeclaration(): Sequence<KSDeclaration> =
findExpects().asSequence().map {
it.toKSDeclaration()
}
// TODO: cross module resolution
fun DeclarationDescriptor.findActualsInKSDeclaration(): Sequence<KSDeclaration> =
findActuals(this.module).asSequence().map {
it.toKSDeclaration()
}
fun MemberDescriptor.toKSDeclaration(): KSDeclaration =
when (val psi = findPsi()) {
is KtClassOrObject -> KSClassDeclarationImpl.getCached(psi)
is KtFunction -> KSFunctionDeclarationImpl.getCached(psi)
is KtProperty -> KSPropertyDeclarationImpl.getCached((psi))
is KtTypeAlias -> KSTypeAliasImpl.getCached(psi)
is PsiClass -> KSClassDeclarationJavaImpl.getCached(psi)
is PsiMethod -> KSFunctionDeclarationJavaImpl.getCached(psi)
is PsiField -> KSPropertyDeclarationJavaImpl.getCached(psi)
else -> when (this) {
is ClassDescriptor -> KSClassDeclarationDescriptorImpl.getCached(this)
is FunctionDescriptor -> KSFunctionDeclarationDescriptorImpl.getCached(this)
is PropertyDescriptor -> KSPropertyDeclarationDescriptorImpl.getCached(this)
else -> throw IllegalStateException("Unknown expect/actual implementation")
}
}
/**
* [NewTypeSubstitutor] handles variance better than [TypeSubstitutor] so we use it when subtituting
* types in [ResolverImpl.asMemberOf] implementations.
*/
private fun TypeSubstitutor.toNewSubstitutor() = composeWith(
org.jetbrains.kotlin.resolve.calls.inference.components.EmptySubstitutor
)
private fun KotlinType.createTypeSubstitutor(): NewTypeSubstitutor {
return SubstitutionUtils.buildDeepSubstitutor(this).toNewSubstitutor()
}
/**
* Extracts the identifier from a module Name.
*
* One caveat here is that kotlin passes a special name into the plugin which cannot be used as an identifier.
* On the other hand, to construct the correct TypeMapper, we need a non-special name.
* This function extracts the non-special name from a given name if it is special.
*
* @see: https://github.com/JetBrains/kotlin/blob/master/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/TopDownAnalyzerFacadeForJVM.kt#L305
*/
private fun Name.getNonSpecialIdentifier(): String {
// the analyzer might pass down a special name which will break type mapper name computations.
// If it is a special name, we turn it back to an id
if (!isSpecial || asString().isBlank()) {
return asString()
}
// special names starts with a `<` and usually end with `>`
return if (asString().last() == '>') {
asString().substring(1, asString().length - 1)
} else {
asString().substring(1)
}
}
private inline fun MemberScope.findEnclosedDescriptor(
kindFilter: DescriptorKindFilter,
crossinline filter: (DeclarationDescriptor) -> Boolean,
): DeclarationDescriptor? {
return getContributedDescriptors(
kindFilter = kindFilter
).firstOrNull(filter)
}
private inline fun ClassDescriptor.findEnclosedDescriptor(
kindFilter: DescriptorKindFilter,
crossinline filter: (DeclarationDescriptor) -> Boolean,
): DeclarationDescriptor? {
return this.unsubstitutedMemberScope.findEnclosedDescriptor(
kindFilter = kindFilter,
filter = filter
) ?: this.staticScope.findEnclosedDescriptor(
kindFilter = kindFilter,
filter = filter
) ?: constructors.firstOrNull {
kindFilter.accepts(it) && filter(it)
}
}
internal fun KSAnnotated.findAnnotationFromUseSiteTarget(): Sequence<KSAnnotation> {
return when (this) {
is KSPropertyGetter -> (this.receiver as? KSDeclarationImpl)?.let {
it.originalAnnotations.asSequence().filter { it.useSiteTarget == AnnotationUseSiteTarget.GET }
}
is KSPropertySetter -> (this.receiver as? KSDeclarationImpl)?.let {
it.originalAnnotations.asSequence().filter { it.useSiteTarget == AnnotationUseSiteTarget.SET }
}
is KSValueParameter -> {
var parent = when (this) {
is KSValueParameterSyntheticImpl -> this.owner
is KSValueParameterImpl -> this.ktParameter.findParentAnnotated()
else -> null
}
// TODO: eliminate annotationsFromParents to make this fully sequence.
val annotationsFromParents = mutableListOf<KSAnnotation>()
(parent as? KSPropertyAccessorImpl)?.let {
annotationsFromParents.addAll(
it.originalAnnotations.asSequence()
.filter { it.useSiteTarget == AnnotationUseSiteTarget.SETPARAM }
)
parent = (parent as KSPropertyAccessorImpl).receiver
}
(parent as? KSPropertyDeclarationImpl)?.let {
annotationsFromParents.addAll(
it.originalAnnotations.asSequence()
.filter { it.useSiteTarget == AnnotationUseSiteTarget.SETPARAM }
)
}
annotationsFromParents.asSequence()
}
else -> emptySequence()
} ?: emptySequence()
}
// Resolve to underlying type.
// Only slightly slower than resolving a plain type, because everything is resolved and cached in the first resolve().
// FIXME: add a resolution mode in resolveUserType() to resolve to underlying type directly.
internal fun KSTypeReference.resolveToUnderlying(): KSType {
var candidate = resolve()
var declaration = candidate.declaration
while (declaration is KSTypeAlias) {
candidate = declaration.type.resolve()
declaration = candidate.declaration
}
return candidate
}
// TODO: Remove this after upgrading to Kotlin 1.8.20.
// Temporary work around for https://github.com/google/ksp/issues/1034
// Force resolve outer most class for Java nested classes.
internal fun JavaClass.workaroundForNested(
lazyJavaResolverContext: LazyJavaResolverContext = ResolverImpl.instance!!.lazyJavaResolverContext
) {
var outerMost = outerClass
while (outerMost?.outerClass != null) {
outerMost = outerMost.outerClass
}
outerMost?.classId?.let { lazyJavaResolverContext.components.finder.findClass(it) }
}