[FIR] Support several annotation class diagnostics

This commit is contained in:
Mikhail Likholetov
2020-05-20 21:40:24 +03:00
committed by Mikhail Glukhikh
parent c112d37ac1
commit 80cd26c9df
11 changed files with 223 additions and 29 deletions
@@ -11,10 +11,10 @@ import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
object DeclarationCheckers {
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf(
FirAnnotationClassDeclarationChecker,
FirModifierChecker
)
val MEMBER_DECLARATIONS: List<FirDeclarationChecker<FirMemberDeclaration>> = DECLARATIONS + listOf(
FirAnnotationClassDeclarationChecker,
FirInfixFunctionDeclarationChecker,
FirExposedVisibilityChecker
)
@@ -9,6 +9,7 @@ import com.intellij.lang.LighterASTNode
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassKind.ANNOTATION_CLASS
import org.jetbrains.kotlin.descriptors.ClassKind.ENUM_CLASS
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticFactory0
@@ -16,11 +17,19 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.FirLightSourceElement
import org.jetbrains.kotlin.fir.FirPsiSourceElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveArrayTypeByElementType
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.primitiveTypes
import org.jetbrains.kotlin.fir.symbols.StandardClassIds.unsignedTypes
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.KtNodeTypes.FUN
import org.jetbrains.kotlin.KtNodeTypes.VALUE_PARAMETER
import org.jetbrains.kotlin.lexer.KtTokens.VAL_KEYWORD
import org.jetbrains.kotlin.lexer.KtTokens.VAR_KEYWORD
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtParameter
object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclaration>() {
@@ -28,13 +37,15 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
if (declaration !is FirRegularClass) return
if (declaration.classKind != ANNOTATION_CLASS) return
if (declaration.isLocal) reporter.report(declaration.source, FirErrors.LOCAL_ANNOTATION_CLASS_ERROR)
for (it in declaration.declarations) {
when {
it is FirConstructor && it.isPrimary -> {
for (parameter in it.valueParameters)
for (parameter in it.valueParameters) {
when (val parameterSourceElement = parameter.source) {
is FirPsiSourceElement<*> -> {
val parameterPsiElement = parameterSourceElement.psi as KtParameter
if (!parameterPsiElement.hasValOrVar())
reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
else if (parameterPsiElement.isMutable)
@@ -43,12 +54,52 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
is FirLightSourceElement -> {
val kidsRef = Ref<Array<LighterASTNode?>>()
parameterSourceElement.tree.getChildren(parameterSourceElement.element, kidsRef)
if (kidsRef.get().any { it?.tokenType == VAR_KEYWORD })
reporter.report(parameterSourceElement, FirErrors.VAR_ANNOTATION_PARAMETER)
else if (kidsRef.get().all { it?.tokenType != VAL_KEYWORD })
reporter.report(parameterSourceElement, FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
}
}
val typeRef = parameter.returnTypeRef
val coneType = typeRef.coneTypeSafe<ConeLookupTagBasedType>()
val classId = coneType?.classId
if (coneType != null) when {
classId == ClassId.fromString("<error>") -> {
// TODO: replace with UNRESOLVED_REFERENCE check
}
coneType.isNullable -> {
reporter.report(typeRef.source, FirErrors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER)
}
classId in primitiveTypes -> {
// DO NOTHING: primitives are allowed as annotation class parameter
}
classId in unsignedTypes -> {
// TODO: replace with EXPERIMENTAL_UNSIGNED_LITERALS check
}
classId == StandardClassIds.KClass -> {
// DO NOTHING: KClass is allowed
}
classId == StandardClassIds.String -> {
// DO NOTHING: String is allowed
}
classId in primitiveArrayTypeByElementType.values -> {
// DO NOTHING: primitive arrays are allowed
}
classId == StandardClassIds.Array -> {
if (!isAllowedArray(typeRef, context.session))
reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
}
isAllowedClassKind(coneType, context.session) -> {
// DO NOTHING: annotation or enum classes are allowed
}
else -> {
reporter.report(typeRef.source, FirErrors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
}
}
}
}
it is FirRegularClass -> {
// DO NOTHING: nested annotation classes are allowed in 1.3+
@@ -65,7 +116,46 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker<FirDeclarati
}
}
}
}
private fun isAllowedClassKind(cone: ConeLookupTagBasedType, session: FirSession): Boolean {
val typeRefClassKind = (cone.lookupTag.toSymbol(session)
?.fir as? FirRegularClass)
?.classKind
?: return false
return typeRefClassKind == ANNOTATION_CLASS || typeRefClassKind == ENUM_CLASS
}
private fun isAllowedArray(typeRef: FirTypeRef, session: FirSession): Boolean {
val typeArguments = typeRef.coneTypeUnsafe<ConeKotlinType>().typeArguments
if (typeArguments.size != 1) return false
val arrayType = (typeArguments[0] as? ConeKotlinTypeProjection)
?.type
?: return false
if (arrayType.isNullable) return false
val arrayTypeClassId = arrayType.classId
when {
arrayTypeClassId == StandardClassIds.KClass -> {
// KClass is allowed
return true
}
arrayTypeClassId == StandardClassIds.String -> {
// String is allowed
return true
}
isAllowedClassKind(arrayType as ConeLookupTagBasedType, session) -> {
// annotation or enum classes are allowed
return true
}
}
return false
}
private inline fun <reified T : FirSourceElement, P : PsiElement> DiagnosticReporter.report(
@@ -54,8 +54,11 @@ object FirErrors {
val NON_PRIVATE_CONSTRUCTOR_IN_SEALED by existing<FirSourceElement, PsiElement>(Errors.NON_PRIVATE_CONSTRUCTOR_IN_SEALED)
val ANNOTATION_CLASS_MEMBER by existing<FirSourceElement, PsiElement>(Errors.ANNOTATION_CLASS_MEMBER)
val ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT by existing<FirSourceElement, KtExpression>(Errors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT)
val LOCAL_ANNOTATION_CLASS_ERROR by existing<FirSourceElement, KtClassOrObject>(Errors.LOCAL_ANNOTATION_CLASS_ERROR)
val MISSING_VAL_ON_ANNOTATION_PARAMETER by existing<FirSourceElement, KtParameter>(Errors.MISSING_VAL_ON_ANNOTATION_PARAMETER)
val NULLABLE_TYPE_OF_ANNOTATION_MEMBER by existing<FirSourceElement, KtTypeReference>(Errors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER)
val INVALID_TYPE_OF_ANNOTATION_MEMBER by existing<FirSourceElement, KtTypeReference>(Errors.INVALID_TYPE_OF_ANNOTATION_MEMBER)
val VAR_ANNOTATION_PARAMETER by existing<FirSourceElement, KtParameter>(Errors.VAR_ANNOTATION_PARAMETER)
// Exposed visibility group