Refactor and improve code obtaining actual Java annotation parameter values

Extract Java-specific code into module 'frontend.java' and use already
existing JavaAnnotationArgument facilities to determine the default
parameter value in an actual Java annotation class.

Annotation arguments are not yet supported because to create an instance
of AnnotationValue, we need a descriptor, which is not available at this
point.

 #KT-22704 In Progress
 #KT-28077 Open
This commit is contained in:
Alexander Udalov
2018-10-18 13:13:25 +02:00
parent c08540175b
commit 49d6a7a7cb
13 changed files with 342 additions and 66 deletions
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.resolve.checkers
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.psi.PsiAnnotationMethod
import com.intellij.psi.PsiCall
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiLiteral
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.*
@@ -30,7 +27,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.constants.ConstantValue
import org.jetbrains.kotlin.resolve.descriptorUtil.isAnnotationConstructor
import org.jetbrains.kotlin.resolve.descriptorUtil.isPrimaryConstructorOfInlineClass
import org.jetbrains.kotlin.resolve.descriptorUtil.module
@@ -39,12 +36,15 @@ import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compati
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Compatible
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.ifEmpty
import java.io.File
object ExpectedActualDeclarationChecker : DeclarationChecker {
val OPTIONAL_EXPECTATION_FQ_NAME = FqName("kotlin.OptionalExpectation")
class ExpectedActualDeclarationChecker(val argumentExtractors: List<ActualAnnotationArgumentExtractor> = emptyList()) : DeclarationChecker {
interface ActualAnnotationArgumentExtractor {
fun extractActualValue(argument: PsiElement, expectedType: KotlinType): ConstantValue<*>?
}
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (!context.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)) return
@@ -96,27 +96,6 @@ object ExpectedActualDeclarationChecker : DeclarationChecker {
}
}
@JvmStatic
fun isOptionalAnnotationClass(descriptor: DeclarationDescriptor): Boolean =
descriptor is ClassDescriptor &&
descriptor.kind == ClassKind.ANNOTATION_CLASS &&
descriptor.isExpect &&
descriptor.annotations.hasAnnotation(OPTIONAL_EXPECTATION_FQ_NAME)
// TODO: move to some other place which is accessible both from backend-common and js.serializer
@JvmStatic
fun shouldGenerateExpectClass(descriptor: ClassDescriptor): Boolean {
assert(descriptor.isExpect) { "Not an expected class: $descriptor" }
if (ExpectedActualDeclarationChecker.isOptionalAnnotationClass(descriptor)) {
with(ExpectedActualResolver) {
return descriptor.findCompatibleActualForExpected(descriptor.module).isEmpty()
}
}
return false
}
private fun ExpectActualTracker.reportExpectActual(expected: MemberDescriptor, actualMembers: Sequence<MemberDescriptor>) {
if (this is ExpectActualTracker.DoNothing) return
@@ -133,9 +112,6 @@ object ExpectedActualDeclarationChecker : DeclarationChecker {
.safeAs<PsiSourceFile>()
?.run { VfsUtilCore.virtualToIoFile(psiFile.virtualFile) }
fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG }
private fun checkActualDeclarationHasExpected(
reportOn: KtNamedDeclaration, descriptor: MemberDescriptor, trace: BindingTrace, checkActual: Boolean
) {
@@ -257,12 +233,9 @@ object ExpectedActualDeclarationChecker : DeclarationChecker {
val actualParameter = DescriptorToSourceUtils.descriptorToDeclaration(actualParameterDescriptor)
val expectedValue = trace.bindingContext.get(BindingContext.COMPILE_TIME_VALUE, expectedParameter.defaultValue)
?.toConstantValue(expectedParameterDescriptor.type)
if (javaDefaultValueEqualsExpectedValue(actualParameter, trace, expectedParameter, expectedValue)) continue
val actualValue = (actualParameter as? KtParameter)?.let { parameter ->
trace.bindingContext.get(BindingContext.COMPILE_TIME_VALUE, parameter.defaultValue)
}
val actualValue = getActualAnnotationParameterValue(actualParameter, trace.bindingContext, expectedParameterDescriptor.type)
if (expectedValue != actualValue) {
val target = (actualParameter as? KtParameter)?.defaultValue ?: (reportOn as? KtTypeAlias)?.nameIdentifier ?: reportOn
trace.report(Errors.ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE.on(target, actualParameterDescriptor))
@@ -271,19 +244,47 @@ object ExpectedActualDeclarationChecker : DeclarationChecker {
}
}
private fun javaDefaultValueEqualsExpectedValue(
actualParameter: PsiElement?, trace: BindingTrace, expectedParameter: KtParameter, expectedValue: CompileTimeConstant<*>?
): Boolean {
val actualPsi = (actualParameter as? PsiAnnotationMethod)?.defaultValue ?: return false
val expectedType = trace.bindingContext.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expectedParameter.defaultValue) ?: return false
val actualValue = when (actualPsi) {
is PsiLiteral -> actualPsi.value
is PsiCall -> {
//TODO: arrays and annotations
return false
}
else -> return false
private fun getActualAnnotationParameterValue(
actualParameter: PsiElement?, bindingContext: BindingContext, expectedType: KotlinType
): ConstantValue<*>? {
if (actualParameter is KtParameter) {
return bindingContext.get(BindingContext.COMPILE_TIME_VALUE, actualParameter.defaultValue)?.toConstantValue(expectedType)
}
return (actualValue == expectedValue?.getValue(expectedType))
if (actualParameter != null) {
for (extractor in argumentExtractors) {
extractor.extractActualValue(actualParameter, expectedType)?.let { return it }
}
}
return null
}
companion object {
val OPTIONAL_EXPECTATION_FQ_NAME = FqName("kotlin.OptionalExpectation")
@JvmStatic
fun isOptionalAnnotationClass(descriptor: DeclarationDescriptor): Boolean =
descriptor is ClassDescriptor &&
descriptor.kind == ClassKind.ANNOTATION_CLASS &&
descriptor.isExpect &&
descriptor.annotations.hasAnnotation(OPTIONAL_EXPECTATION_FQ_NAME)
// TODO: move to some other place which is accessible both from backend-common and js.serializer
@JvmStatic
fun shouldGenerateExpectClass(descriptor: ClassDescriptor): Boolean {
assert(descriptor.isExpect) { "Not an expected class: $descriptor" }
if (isOptionalAnnotationClass(descriptor)) {
with(ExpectedActualResolver) {
return descriptor.findCompatibleActualForExpected(descriptor.module).isEmpty()
}
}
return false
}
fun Map<out Compatibility, Collection<MemberDescriptor>>.allStrongIncompatibilities(): Boolean =
this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG }
}
}