From 80cd26c9df98e7569ace3af05033d703014debe2 Mon Sep 17 00:00:00 2001 From: Mikhail Likholetov Date: Wed, 20 May 2020 21:40:24 +0300 Subject: [PATCH] [FIR] Support several annotation class diagnostics --- .../diagnostics/typeOfAnnotationMember.kt | 23 +++++ .../diagnostics/typeOfAnnotationMember.txt | 68 ++++++++++++++ .../testData/resolve/multifile/Annotations.kt | 2 +- .../fir/FirDiagnosticsTestGenerated.java | 5 + ...DiagnosticsWithLightTreeTestGenerated.java | 5 + .../declaration/DeclarationCheckers.kt | 2 +- .../FirAnnotationClassDeclarationChecker.kt | 92 ++++++++++++++++++- .../fir/analysis/diagnostics/FirErrors.kt | 3 + ...invalidTypesInAnnotationConstructor.fir.kt | 44 ++++----- .../inlineClassesInsideAnnotations.fir.kt | 6 +- ...arargsOnParametersOfInlineClassType.fir.kt | 2 +- 11 files changed, 223 insertions(+), 29 deletions(-) create mode 100644 compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt create mode 100644 compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.txt diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt new file mode 100644 index 00000000000..75dc16cd3dc --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt @@ -0,0 +1,23 @@ +class Foo + +enum class Enum { + Bar +} + +annotation class Ann1( + val p1: Array, + val p2: Array, + val p3: Array, + val p4: Array, + val p5: Foo, + vararg val p6: Class<*> +) + +annotation class Ann2( + val p1: Int?, + val p2: String?, + val p3: IntArray?, + val p4: Array?, + val p5: Ann1?, + val p6: Enum? +) \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.txt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.txt new file mode 100644 index 00000000000..4ab55533d58 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.txt @@ -0,0 +1,68 @@ +FILE: typeOfAnnotationMember.kt + public final class Foo : R|kotlin/Any| { + public constructor(): R|Foo| { + super() + } + + } + public final enum class Enum : R|kotlin/Enum| { + private constructor(): R|Enum| { + super|>() + } + + public final static enum entry Bar: R|Enum| + public final static fun values(): R|kotlin/Array| { + } + + public final static fun valueOf(value: R|kotlin/String|): R|Enum| { + } + + } + public final annotation class Ann1 : R|kotlin/Annotation| { + public constructor(p1: R|kotlin/Array|, p2: R|kotlin/Array|, p3: R|kotlin/Array|, p4: R|kotlin/Array|, p5: R|Foo|, vararg p6: R|kotlin/Array>|): R|Ann1| { + super() + } + + public final val p1: R|kotlin/Array| = R|/p1| + public get(): R|kotlin/Array| + + public final val p2: R|kotlin/Array| = R|/p2| + public get(): R|kotlin/Array| + + public final val p3: R|kotlin/Array| = R|/p3| + public get(): R|kotlin/Array| + + public final val p4: R|kotlin/Array| = R|/p4| + public get(): R|kotlin/Array| + + public final val p5: R|Foo| = R|/p5| + public get(): R|Foo| + + public final val p6: R|kotlin/Array>| = R|/p6| + public get(): R|kotlin/Array>| + + } + public final annotation class Ann2 : R|kotlin/Annotation| { + public constructor(p1: R|kotlin/Int?|, p2: R|kotlin/String?|, p3: R|kotlin/IntArray?|, p4: R|kotlin/Array?|, p5: R|Ann1?|, p6: R|Enum?|): R|Ann2| { + super() + } + + public final val p1: R|kotlin/Int?| = R|/p1| + public get(): R|kotlin/Int?| + + public final val p2: R|kotlin/String?| = R|/p2| + public get(): R|kotlin/String?| + + public final val p3: R|kotlin/IntArray?| = R|/p3| + public get(): R|kotlin/IntArray?| + + public final val p4: R|kotlin/Array?| = R|/p4| + public get(): R|kotlin/Array?| + + public final val p5: R|Ann1?| = R|/p5| + public get(): R|Ann1?| + + public final val p6: R|Enum?| = R|/p6| + public get(): R|Enum?| + + } diff --git a/compiler/fir/analysis-tests/testData/resolve/multifile/Annotations.kt b/compiler/fir/analysis-tests/testData/resolve/multifile/Annotations.kt index 038f2326470..ef01d9fd690 100644 --- a/compiler/fir/analysis-tests/testData/resolve/multifile/Annotations.kt +++ b/compiler/fir/analysis-tests/testData/resolve/multifile/Annotations.kt @@ -11,7 +11,7 @@ annotation class WithString(val s: String) annotation class Complex(val wi: WithInt, val ws: WithString) -annotation class VeryComplex(val f: Float, val d: Double, val b: Boolean, val l: Long, val n: Int?) +annotation class VeryComplex(val f: Float, val d: Double, val b: Boolean, val l: Long, val n: Int?) // FILE: main.kt diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index 15852866e1c..4e204484fa4 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -871,6 +871,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt"); } + @TestMetadata("typeOfAnnotationMember.kt") + public void testTypeOfAnnotationMember() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt"); + } + @TestMetadata("valOnAnnotationParameter.kt") public void testValOnAnnotationParameter() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index b2394bce511..af18df6d98b 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -871,6 +871,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/superclassNotAccessibleFromInterface.kt"); } + @TestMetadata("typeOfAnnotationMember.kt") + public void testTypeOfAnnotationMember() throws Exception { + runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt"); + } + @TestMetadata("valOnAnnotationParameter.kt") public void testValOnAnnotationParameter() throws Exception { runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DeclarationCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DeclarationCheckers.kt index fbb8a6ee34c..354a382a1a2 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DeclarationCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/DeclarationCheckers.kt @@ -11,10 +11,10 @@ import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration object DeclarationCheckers { val DECLARATIONS: List> = listOf( - FirAnnotationClassDeclarationChecker, FirModifierChecker ) val MEMBER_DECLARATIONS: List> = DECLARATIONS + listOf( + FirAnnotationClassDeclarationChecker, FirInfixFunctionDeclarationChecker, FirExposedVisibilityChecker ) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt index 9c58152c23d..8eb93cc5248 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirAnnotationClassDeclarationChecker.kt @@ -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() { @@ -28,13 +37,15 @@ object FirAnnotationClassDeclarationChecker : FirDeclarationChecker { - 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 { val kidsRef = Ref>() 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() + val classId = coneType?.classId + + if (coneType != null) when { + classId == ClassId.fromString("") -> { + // 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().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 DiagnosticReporter.report( diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index d592b59ecb5..9d65be88378 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -54,8 +54,11 @@ object FirErrors { val NON_PRIVATE_CONSTRUCTOR_IN_SEALED by existing(Errors.NON_PRIVATE_CONSTRUCTOR_IN_SEALED) val ANNOTATION_CLASS_MEMBER by existing(Errors.ANNOTATION_CLASS_MEMBER) + val ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT by existing(Errors.ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT) val LOCAL_ANNOTATION_CLASS_ERROR by existing(Errors.LOCAL_ANNOTATION_CLASS_ERROR) val MISSING_VAL_ON_ANNOTATION_PARAMETER by existing(Errors.MISSING_VAL_ON_ANNOTATION_PARAMETER) + val NULLABLE_TYPE_OF_ANNOTATION_MEMBER by existing(Errors.NULLABLE_TYPE_OF_ANNOTATION_MEMBER) + val INVALID_TYPE_OF_ANNOTATION_MEMBER by existing(Errors.INVALID_TYPE_OF_ANNOTATION_MEMBER) val VAR_ANNOTATION_PARAMETER by existing(Errors.VAR_ANNOTATION_PARAMETER) // Exposed visibility group diff --git a/compiler/testData/diagnostics/tests/annotations/invalidTypesInAnnotationConstructor.fir.kt b/compiler/testData/diagnostics/tests/annotations/invalidTypesInAnnotationConstructor.fir.kt index 73a5332afb9..ac9b90e906d 100644 --- a/compiler/testData/diagnostics/tests/annotations/invalidTypesInAnnotationConstructor.fir.kt +++ b/compiler/testData/diagnostics/tests/annotations/invalidTypesInAnnotationConstructor.fir.kt @@ -25,11 +25,11 @@ annotation class Ann4(val p1: IntArray, annotation class Ann5(val p1: MyEnum) -annotation class Ann6(val p: Class<*>) +annotation class Ann6(val p: Class<*>) annotation class Ann7(val p: RetentionPolicy) annotation class Ann8(val p1: Array, - val p2: Array>, + val p2: Array>, val p3: Array, val p4: Array) @@ -39,32 +39,32 @@ annotation class Ann9( // INCORRECT -annotation class InAnn1(val p1: Int?, - val p3: Short?, - val p4: Long?, - val p5: Double?, - val p6: Float?, - val p7: Char?, - val p8: Boolean?) +annotation class InAnn1(val p1: Int?, + val p3: Short?, + val p4: Long?, + val p5: Double?, + val p6: Float?, + val p7: Char?, + val p8: Boolean?) -annotation class InAnn4(val p1: Array, - val p2: Array?) +annotation class InAnn4(val p1: Array, + val p2: Array?) -annotation class InAnn6(val p: Class<*>?) -annotation class InAnn7(val p: RetentionPolicy?) -annotation class InAnn8(val p1: Array, - val p2: Array, - val p3: Array, - val p4: Array) +annotation class InAnn6(val p: Class<*>?) +annotation class InAnn7(val p: RetentionPolicy?) +annotation class InAnn8(val p1: Array, + val p2: Array, + val p3: Array, + val p4: Array) -annotation class InAnn9(val p: MyClass) +annotation class InAnn9(val p: MyClass) -annotation class InAnn10(val p1: String?) -annotation class InAnn11(val p1: Ann1?) -annotation class InAnn12(val p1: MyEnum?) +annotation class InAnn10(val p1: String?) +annotation class InAnn11(val p1: Ann1?) +annotation class InAnn12(val p1: MyEnum?) annotation class InAnn13(vararg val p1: String, - vararg val p2: Class<*>, + vararg val p2: Class<*>, vararg val p3: MyEnum, vararg val p4: Ann1, vararg val p5: Int) diff --git a/compiler/testData/diagnostics/tests/inlineClasses/inlineClassesInsideAnnotations.fir.kt b/compiler/testData/diagnostics/tests/inlineClasses/inlineClassesInsideAnnotations.fir.kt index 861f728fed8..a3a877e50f5 100644 --- a/compiler/testData/diagnostics/tests/inlineClasses/inlineClassesInsideAnnotations.fir.kt +++ b/compiler/testData/diagnostics/tests/inlineClasses/inlineClassesInsideAnnotations.fir.kt @@ -5,8 +5,8 @@ import kotlin.reflect.KClass inline class MyInt(val x: Int) inline class MyString(val x: String) -annotation class Ann1(val a: MyInt) -annotation class Ann2(val a: Array) -annotation class Ann3(vararg val a: MyInt) +annotation class Ann1(val a: MyInt) +annotation class Ann2(val a: Array) +annotation class Ann3(vararg val a: MyInt) annotation class Ann4(val a: KClass) \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/inlineClasses/varargsOnParametersOfInlineClassType.fir.kt b/compiler/testData/diagnostics/tests/inlineClasses/varargsOnParametersOfInlineClassType.fir.kt index 69737965c97..a6faedc90ed 100644 --- a/compiler/testData/diagnostics/tests/inlineClasses/varargsOnParametersOfInlineClassType.fir.kt +++ b/compiler/testData/diagnostics/tests/inlineClasses/varargsOnParametersOfInlineClassType.fir.kt @@ -18,4 +18,4 @@ class B(vararg val s: Foo) { constructor(a: Int, vararg s: Foo) : this(*s) } -annotation class Ann(vararg val f: Foo) \ No newline at end of file +annotation class Ann(vararg val f: Foo) \ No newline at end of file