[FE] Prohibit actual typealias to certain compiler annotations

^KT-58554
This commit is contained in:
Roman Efremov
2023-07-13 11:29:24 +02:00
committed by Space Team
parent f4a648aa3e
commit a79282cec1
21 changed files with 198 additions and 0 deletions
@@ -3464,6 +3464,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION) { firDiagnostic ->
ActualTypealiasToSpecialAnnotationImpl(
firDiagnostic.a,
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION) { firDiagnostic ->
InitializerRequiredForDestructuringDeclarationImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -2420,6 +2420,11 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = ExpectActualOptInAnnotation::class
}
interface ActualTypealiasToSpecialAnnotation : KtFirDiagnostic<KtTypeAlias> {
override val diagnosticClass get() = ActualTypealiasToSpecialAnnotation::class
val typealiasedClassId: ClassId
}
interface InitializerRequiredForDestructuringDeclaration : KtFirDiagnostic<KtDestructuringDeclaration> {
override val diagnosticClass get() = InitializerRequiredForDestructuringDeclaration::class
}
@@ -2918,6 +2918,12 @@ internal class ExpectActualOptInAnnotationImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtNamedDeclaration>(firDiagnostic, token), KtFirDiagnostic.ExpectActualOptInAnnotation
internal class ActualTypealiasToSpecialAnnotationImpl(
override val typealiasedClassId: ClassId,
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtTypeAlias>(firDiagnostic, token), KtFirDiagnostic.ActualTypealiasToSpecialAnnotation
internal class InitializerRequiredForDestructuringDeclarationImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -20,6 +20,12 @@ import java.util.regex.Pattern;
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform")
@TestDataPath("$PROJECT_ROOT")
public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends AbstractFirLightTreeWithActualizerDiagnosticsTest {
@Test
@TestMetadata("actualTypealiasToSpecialAnnotation.kt")
public void testActualTypealiasToSpecialAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt");
}
@Test
public void testAllFilesPresentInMultiplatform() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
@@ -20,6 +20,12 @@ import java.util.regex.Pattern;
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform")
@TestDataPath("$PROJECT_ROOT")
public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFirPsiWithActualizerDiagnosticsTest {
@Test
@TestMetadata("actualTypealiasToSpecialAnnotation.kt")
public void testActualTypealiasToSpecialAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt");
}
@Test
public void testAllFilesPresentInMultiplatform() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.fir.FirModuleData
import org.jetbrains.kotlin.fir.PrivateForInline
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.*
import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -1176,6 +1177,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val NOT_A_MULTIPLATFORM_COMPILATION by error<PsiElement>()
val EXPECT_ACTUAL_OPT_IN_ANNOTATION by error<KtNamedDeclaration>(PositioningStrategy.EXPECT_ACTUAL_MODIFIER)
val ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION by error<KtTypeAlias>(PositioningStrategy.TYPEALIAS_TYPE_REFERENCE) {
parameter<ClassId>("typealiasedClassId")
}
}
val DESTRUCTING_DECLARATION by object : DiagnosticGroup("Destructuring declaration") {
@@ -114,6 +114,7 @@ enum class PositioningStrategy(private val strategy: String? = null) {
INLINE_FUN_MODIFIER,
CALL_ELEMENT_WITH_DOT,
EXPECT_ACTUAL_MODIFIER,
TYPEALIAS_TYPE_REFERENCE,
;
val expressionToCreate get() = "SourceElementPositioningStrategies.${strategy ?: name}"
@@ -627,6 +627,7 @@ object FirErrors {
val ACTUAL_MISSING by error0<KtNamedDeclaration>(SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME)
val NOT_A_MULTIPLATFORM_COMPILATION by error0<PsiElement>()
val EXPECT_ACTUAL_OPT_IN_ANNOTATION by error0<KtNamedDeclaration>(SourceElementPositioningStrategies.EXPECT_ACTUAL_MODIFIER)
val ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION by error1<KtTypeAlias, ClassId>(SourceElementPositioningStrategies.TYPEALIAS_TYPE_REFERENCE)
// Destructuring declaration
val INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION by error0<KtDestructuringDeclaration>()
@@ -159,6 +159,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
get() = setOf(
FirTopLevelTypeAliasChecker,
FirActualTypeAliasChecker,
FirActualTypealiasToSpecialAnnotationChecker,
)
override val anonymousFunctionCheckers: Set<FirAnonymousFunctionChecker>
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.classKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirTypeAlias
import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType
import org.jetbrains.kotlin.fir.declarations.utils.isActual
import org.jetbrains.kotlin.fir.types.toSymbol
import org.jetbrains.kotlin.resolve.calls.mpp.ActualTypealiasToSpecialAnnotationUtils.isAnnotationProhibitedInActualTypeAlias
internal object FirActualTypealiasToSpecialAnnotationChecker : FirTypeAliasChecker() {
override fun check(declaration: FirTypeAlias, context: CheckerContext, reporter: DiagnosticReporter) {
if (!declaration.isActual) return
val typealiasedClassSymbol = declaration.expandedConeType?.toSymbol(context.session) ?: return
if (typealiasedClassSymbol.classKind != ClassKind.ANNOTATION_CLASS) {
return
}
val classId = typealiasedClassSymbol.classId
if (isAnnotationProhibitedInActualTypeAlias(classId)) {
reporter.reportOn(declaration.source, FirErrors.ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION, classId, context)
}
}
}
@@ -54,6 +54,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACCESSOR_FOR_DELE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_MISSING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS_NOT_TO_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION
@@ -1847,6 +1848,11 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
map.put(ACTUAL_MISSING, "Declaration must be marked with 'actual'")
map.put(NOT_A_MULTIPLATFORM_COMPILATION, "'expect' and 'actual' declarations can be used only in multiplatform projects. Learn more about Kotlin Multiplatform: https://kotl.in/multiplatform-setup")
map.put(EXPECT_ACTUAL_OPT_IN_ANNOTATION, "Opt-in annotations are prohibited to be `expect` or `actual`. Instead, declare annotation once in common sources.")
map.put(
ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION,
"`actual typealias` to annotation which affects code compilation can lead to incorrect behavior. Instead, use ''{0}'' annotation directly.",
TO_STRING
)
// Destructuring declaration
map.put(INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION, "Initializer required for destructuring declaration")
@@ -1208,6 +1208,17 @@ object LightTreePositioningStrategies {
}
}
val TYPEALIAS_TYPE_REFERENCE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() {
override fun mark(
node: LighterASTNode,
startOffset: Int,
endOffset: Int,
tree: FlyweightCapableTreeStructure<LighterASTNode>
): List<TextRange> {
val nodeToMark = tree.findChildByType(node, KtNodeTypes.TYPE_REFERENCE) ?: node
return markElement(nodeToMark, startOffset, endOffset, tree, node)
}
}
}
fun KtSourceElement.hasValOrVar(): Boolean =
@@ -1031,6 +1031,13 @@ object PositioningStrategies {
}
}
@JvmField
val TYPEALIAS_TYPE_REFERENCE = object : PositioningStrategy<KtTypeAlias>() {
override fun mark(element: KtTypeAlias): List<TextRange> {
return markElement(element.getTypeReference() ?: element)
}
}
/**
* @param locateReferencedName whether to remove any nested parentheses while locating the reference element. This is useful for
* diagnostics on super and unresolved references. For example, with the following, only the part inside the parentheses should be
@@ -384,4 +384,9 @@ object SourceElementPositioningStrategies {
LightTreePositioningStrategies.CALL_ELEMENT_WITH_DOT,
PositioningStrategies.CALL_ELEMENT_WITH_DOT
)
val TYPEALIAS_TYPE_REFERENCE = SourceElementPositioningStrategy(
LightTreePositioningStrategies.TYPEALIAS_TYPE_REFERENCE,
PositioningStrategies.TYPEALIAS_TYPE_REFERENCE,
)
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.lexer.KtKeywordToken;
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.metadata.deserialization.VersionRequirement;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
@@ -837,6 +838,8 @@ public interface Errors {
DiagnosticFactory0<PsiElement> OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> NOT_A_MULTIPLATFORM_COMPILATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtNamedDeclaration> EXPECT_ACTUAL_OPT_IN_ANNOTATION = DiagnosticFactory0.create(ERROR, EXPECT_ACTUAL_MODIFIER);
DiagnosticFactory1<KtTypeAlias, ClassId> ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION =
DiagnosticFactory1.create(ERROR, TYPEALIAS_TYPE_REFERENCE);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -393,6 +393,9 @@ public class DefaultErrorMessages {
MAP.put(OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE, "Declaration annotated with '@OptionalExpectation' can only be used in common module sources");
MAP.put(NOT_A_MULTIPLATFORM_COMPILATION, "'expect' and 'actual' declarations can be used only in multiplatform projects. Learn more about Kotlin Multiplatform: https://kotl.in/multiplatform-setup");
MAP.put(EXPECT_ACTUAL_OPT_IN_ANNOTATION, "Opt-in annotations are prohibited to be `expect` or `actual`. Instead, declare annotation once in common sources.");
MAP.put(ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION,
"`actual typealias` to annotation which affects code compilation can lead to incorrect behavior. Instead, use ''{0}'' annotation directly.",
TO_STRING);
MAP.put(PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT, "Projections are not allowed on type arguments of functions and properties");
MAP.put(SUPERTYPE_NOT_INITIALIZED, "This type has a constructor, and thus must be initialized here");
@@ -58,6 +58,7 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
DataObjectContentChecker,
EnumEntriesRedeclarationChecker,
VolatileAnnotationChecker,
ActualTypealiasToSpecialAnnotationChecker,
)
private val DEFAULT_CALL_CHECKERS = listOf(
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.TypeAliasDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtTypeAlias
import org.jetbrains.kotlin.resolve.calls.mpp.ActualTypealiasToSpecialAnnotationUtils.isAnnotationProhibitedInActualTypeAlias
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
internal object ActualTypealiasToSpecialAnnotationChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (declaration !is KtTypeAlias || descriptor !is TypeAliasDescriptor || !descriptor.isActual) {
return
}
val classDescriptor = descriptor.classDescriptor ?: return
if (classDescriptor.kind != ClassKind.ANNOTATION_CLASS) {
return
}
val classId = classDescriptor.classId ?: return
if (isAnnotationProhibitedInActualTypeAlias(classId)) {
context.trace.report(Errors.ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION.on(declaration, classId))
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.calls.mpp
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.name.ClassId
object ActualTypealiasToSpecialAnnotationUtils {
/**
* Type aliases may be useful to commonize some platform-specific annotations, e.g. [kotlin.jvm.Synchronized].
* We cannot universally determine at compile time whether annotation is accessible in common code.
* Instead, we prohibit certain package names because we are sure that annotations from them
* are visible in common and there is no point to typealias them.
*/
private val FORBIDDEN_PACKAGES = setOf(
StandardNames.ANNOTATION_PACKAGE_FQ_NAME,
StandardNames.BUILT_INS_PACKAGE_FQ_NAME,
StandardNames.KOTLIN_INTERNAL_FQ_NAME,
)
fun isAnnotationProhibitedInActualTypeAlias(classId: ClassId): Boolean {
return classId.packageFqName in FORBIDDEN_PACKAGES
}
}
@@ -0,0 +1,28 @@
// FIR_IDENTICAL
// WITH_STDLIB
// MODULE: m1-common
// FILE: common.kt
expect annotation class TypealiasToKotlinPkg
internal expect annotation class TypealiasToInternalPkg
expect annotation class TypealiasToAnnotationPkg
expect annotation class TypealiasToPlatformPkg
expect enum class TypealiasNotToAnnotation
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
actual typealias TypealiasToKotlinPkg = <!ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION!>kotlin.Deprecated<!>
@Suppress("INVISIBLE_REFERENCE")
internal actual typealias TypealiasToInternalPkg = <!ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION!>kotlin.internal.RequireKotlin<!>
actual typealias TypealiasToAnnotationPkg = <!ACTUAL_TYPEALIAS_TO_SPECIAL_ANNOTATION!>kotlin.annotation.Target<!>
actual typealias TypealiasToPlatformPkg = kotlin.jvm.Synchronized
typealias NonActualTypealias = kotlin.Deprecated
actual typealias TypealiasNotToAnnotation = kotlin.DeprecationLevel
@@ -22529,6 +22529,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform")
@TestDataPath("$PROJECT_ROOT")
public class Multiplatform {
@Test
@TestMetadata("actualTypealiasToSpecialAnnotation.kt")
public void testActualTypealiasToSpecialAnnotation() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt");
}
@Test
public void testAllFilesPresentInMultiplatform() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true);