K2 JVM: implement Java modules-related checkers

All existing tests use custom test data for FIR because the diagnostic
text in FIR has to have a dot at the end. Also, the K2 checker doesn't
check usages in imports because there are no "import checkers" in K2
right now, this will need to be fixed later if necessary.

 #KT-60797 Fixed
This commit is contained in:
Alexander Udalov
2024-02-15 12:50:41 +01:00
committed by Space Team
parent ac901c6d07
commit 5ca7ab9801
23 changed files with 343 additions and 38 deletions
@@ -7,20 +7,16 @@ package org.jetbrains.kotlin.fir.checkers.generator.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature.*
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.DiagnosticList
import org.jetbrains.kotlin.fir.checkers.generator.diagnostics.model.PositioningStrategy
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.util.PrivateForInline
@Suppress("ClassName", "unused")
@@ -135,6 +131,17 @@ object JVM_DIAGNOSTICS_LIST : DiagnosticList("FirJvmErrors") {
val ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE by error<PsiElement>()
}
val MODULES by object : DiagnosticGroup("JVM Modules") {
val JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE by error<PsiElement> {
parameter<String>("moduleName")
}
val JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE by error<PsiElement>()
val JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE by error<PsiElement>() {
parameter<String>("moduleName")
parameter<String>("packageName")
}
}
val JVM_DEFAULT by object : DiagnosticGroup("JVM Default") {
val JVM_DEFAULT_IN_DECLARATION by error<KtElement>(PositioningStrategy.DECLARATION_SIGNATURE_OR_DEFAULT) {
parameter<String>("annotation")
@@ -99,6 +99,11 @@ object FirJvmErrors {
val JVM_RECORD_EXTENDS_CLASS: KtDiagnosticFactory1<ConeKotlinType> by error1<PsiElement, ConeKotlinType>(SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME)
val ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE: KtDiagnosticFactory0 by error0<PsiElement>()
// JVM Modules
val JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE: KtDiagnosticFactory1<String> by error1<PsiElement, String>()
val JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE: KtDiagnosticFactory0 by error0<PsiElement>()
val JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE: KtDiagnosticFactory2<String, String> by error2<PsiElement, String, String>()
// JVM Default
val JVM_DEFAULT_IN_DECLARATION: KtDiagnosticFactory1<String> by error1<KtElement, String>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT)
val JVM_DEFAULT_WITH_COMPATIBILITY_IN_DECLARATION: KtDiagnosticFactory0 by error0<KtElement>()
@@ -34,6 +34,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICAB
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INNER_JVM_RECORD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_SAM_INTERFACE_CONSTRUCTOR_REFERENCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JAVA_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_DEFAULT_IN_DECLARATION
@@ -182,6 +185,19 @@ object FirJvmErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
map.put(NON_DATA_CLASS_JVM_RECORD, "Only data classes are allowed to be marked as '@JvmRecord'.")
map.put(ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE, "Classes cannot have explicit 'java.lang.Record' supertype.")
map.put(
JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE,
"Symbol is declared in module ''{0}'', which the current module does not depend on.", STRING,
)
map.put(
JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE,
"Symbol is declared in an unnamed module which is not read by current module."
)
map.put(
JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE,
"Symbol is declared in module ''{0}'' which does not export package ''{1}''.", STRING, STRING,
)
map.put(OVERRIDE_CANNOT_BE_STATIC, "Override member cannot be '@JvmStatic' in an object.")
map.put(
JVM_STATIC_NOT_IN_OBJECT_OR_CLASS_COMPANION,
@@ -20,6 +20,7 @@ object JvmExpressionCheckers : ExpressionCheckers() {
FirInterfaceDefaultMethodCallChecker,
FirJavaSamInterfaceConstructorReferenceChecker,
FirQualifiedAccessJavaNullabilityWarningChecker,
FirJvmModuleAccessibilityQualifiedAccessChecker,
)
override val propertyAccessExpressionCheckers: Set<FirPropertyAccessExpressionChecker>
@@ -82,4 +83,9 @@ object JvmExpressionCheckers : ExpressionCheckers() {
get() = setOf(
FirJavaUnnecessaryNotNullChecker,
)
override val resolvedQualifierCheckers: Set<FirResolvedQualifierChecker>
get() = setOf(
FirJvmModuleAccessibilityResolvedQualifierChecker,
)
}
@@ -7,10 +7,12 @@ package org.jetbrains.kotlin.fir.analysis.jvm.checkers
import org.jetbrains.kotlin.fir.analysis.checkers.type.*
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.type.FirFunctionalTypeParameterNameChecker
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.type.FirJvmModuleAccessibilityTypeChecker
object JvmTypeCheckers : TypeCheckers() {
override val typeRefCheckers: Set<FirTypeRefChecker> = setOf(
FirDynamicUnsupportedChecker,
FirFunctionalTypeParameterNameChecker,
FirJvmModuleAccessibilityTypeChecker,
)
}
@@ -0,0 +1,93 @@
/*
* 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.jvm.checkers.expression
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.KtVirtualFileSourceFile
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirQualifiedAccessExpressionChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.utils.sourceElement
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.expressions.toReference
import org.jetbrains.kotlin.fir.java.JavaBinarySourceElement
import org.jetbrains.kotlin.fir.modules.javaModuleResolverProvider
import org.jetbrains.kotlin.fir.packageFqName
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver
object FirJvmModuleAccessibilityQualifiedAccessChecker : FirQualifiedAccessExpressionChecker(MppCheckerKind.Common) {
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val callableSymbol = expression.calleeReference.toResolvedCallableSymbol() ?: return
if (callableSymbol.origin.fromSource) return
val containingClass = callableSymbol.containingClassLookupTag()
if (containingClass != null) {
val containingClassSymbol = containingClass.toFirRegularClassSymbol(context.session) ?: return
checkClassAccess(context, containingClassSymbol, expression, reporter)
} else {
val containerSource = callableSymbol.containerSource as? JvmPackagePartSource ?: return
val virtualFile = (containerSource.knownJvmBinaryClass as? VirtualFileKotlinClass)?.file ?: return
checkPackageAccess(context, virtualFile, containerSource.className.packageFqName, expression, reporter)
}
}
internal fun checkClassAccess(context: CheckerContext, symbol: FirClassSymbol<*>, element: FirElement, reporter: DiagnosticReporter) {
if (symbol.origin.fromSource) return
@OptIn(SymbolInternals::class)
val sourceElement = symbol.fir.sourceElement
val virtualFile = when (sourceElement) {
is JavaBinarySourceElement -> sourceElement.virtualFile
is KotlinJvmBinarySourceElement -> (sourceElement.binaryClass as? VirtualFileKotlinClass)?.file ?: return
else -> return
}
checkPackageAccess(context, virtualFile, symbol.packageFqName(), element, reporter)
}
internal fun checkPackageAccess(
context: CheckerContext,
fileFromPackage: VirtualFile,
referencedPackageFqName: FqName,
element: FirElement,
reporter: DiagnosticReporter,
) {
val fileFromOurModule = (context.containingFile?.sourceFile as? KtVirtualFileSourceFile)?.virtualFile
val diagnostic = context.session.javaModuleResolverProvider.javaModuleResolver.checkAccessibility(
fileFromOurModule, fileFromPackage, referencedPackageFqName
) ?: return
val source = element.toReference(context.session)?.source ?: element.source
when (diagnostic) {
is JavaModuleResolver.AccessError.ModuleDoesNotExportPackage -> {
reporter.reportOn(
source, FirJvmErrors.JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE, diagnostic.dependencyModuleName,
referencedPackageFqName.asString(), context,
)
}
is JavaModuleResolver.AccessError.ModuleDoesNotReadModule -> {
reporter.reportOn(source, FirJvmErrors.JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE, diagnostic.dependencyModuleName, context)
}
JavaModuleResolver.AccessError.ModuleDoesNotReadUnnamedModule -> {
reporter.reportOn(source, FirJvmErrors.JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE, context)
}
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2024 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.jvm.checkers.expression
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirResolvedQualifierChecker
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
object FirJvmModuleAccessibilityResolvedQualifierChecker : FirResolvedQualifierChecker(MppCheckerKind.Common) {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
val symbol = expression.symbol
if (symbol is FirClassSymbol<*>) {
FirJvmModuleAccessibilityQualifiedAccessChecker.checkClassAccess(context, symbol, expression, reporter)
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2024 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.jvm.checkers.type
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.type.FirTypeRefChecker
import org.jetbrains.kotlin.fir.analysis.jvm.checkers.expression.FirJvmModuleAccessibilityQualifiedAccessChecker
import org.jetbrains.kotlin.fir.types.FirTypeRef
import org.jetbrains.kotlin.fir.types.coneTypeOrNull
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
object FirJvmModuleAccessibilityTypeChecker : FirTypeRefChecker(MppCheckerKind.Common) {
override fun check(typeRef: FirTypeRef, context: CheckerContext, reporter: DiagnosticReporter) {
val classSymbol = typeRef.coneTypeOrNull?.toRegularClassSymbol(context.session) ?: return
FirJvmModuleAccessibilityQualifiedAccessChecker.checkClassAccess(context, classSymbol, typeRef, reporter)
}
}
@@ -616,6 +616,9 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"DELEGATION_BY_IN_JVM_RECORD",
"JVM_RECORD_EXTENDS_CLASS",
"ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE",
"JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE",
"JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE",
"JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE",
"JVM_DEFAULT_IN_DECLARATION",
"JVM_DEFAULT_WITH_COMPATIBILITY_IN_DECLARATION",
"JVM_DEFAULT_WITH_COMPATIBILITY_NOT_ON_INTERFACE",