[IR] Implement IR checker for expect actual annotations matching

^KT-58551
This commit is contained in:
Roman Efremov
2023-06-22 12:30:52 +02:00
committed by Space Team
parent b6cae1adcc
commit 2980179bd7
26 changed files with 324 additions and 44 deletions
@@ -7,9 +7,10 @@ package org.jetbrains.kotlin.backend.common
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.backend.common.BackendDiagnosticRenderers.INCOMPATIBILITY
import org.jetbrains.kotlin.backend.common.BackendDiagnosticRenderers.SYMBOL_OWNER_DECLARATION_FQ_NAME
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.error0
import org.jetbrains.kotlin.diagnostics.SourceElementPositioningStrategies.ACTUAL_DECLARATION_NAME
import org.jetbrains.kotlin.diagnostics.error2
import org.jetbrains.kotlin.diagnostics.error3
import org.jetbrains.kotlin.diagnostics.rendering.BaseDiagnosticRendererFactory
@@ -17,6 +18,10 @@ import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers.STRING
import org.jetbrains.kotlin.diagnostics.rendering.Renderer
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.MODULE_WITH_PLATFORM
import org.jetbrains.kotlin.diagnostics.rendering.RootDiagnosticRendererFactory
import org.jetbrains.kotlin.diagnostics.warning2
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualCompatibility
object CommonBackendErrors {
@@ -24,6 +29,7 @@ object CommonBackendErrors {
val MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED by error2<PsiElement, String, String>()
val MANY_IMPL_MEMBER_NOT_IMPLEMENTED by error2<PsiElement, String, String>()
val INCOMPATIBLE_MATCHING by error3<PsiElement, String, String, ExpectActualCompatibility.Incompatible<*>>()
val ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT by warning2<PsiElement, IrSymbol, IrSymbol>()
init {
RootDiagnosticRendererFactory.registerFactory(KtDefaultCommonBackendErrorMessages)
@@ -57,6 +63,12 @@ object KtDefaultCommonBackendErrorMessages : BaseDiagnosticRendererFactory() {
STRING,
INCOMPATIBILITY
)
map.put(
CommonBackendErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
"All annotations from expect declaration `{0}` must be present with the same arguments on actual declaration `{1}` otherwise they have no effect",
SYMBOL_OWNER_DECLARATION_FQ_NAME,
SYMBOL_OWNER_DECLARATION_FQ_NAME,
)
}
}
@@ -64,4 +76,7 @@ object BackendDiagnosticRenderers {
val INCOMPATIBILITY = Renderer<ExpectActualCompatibility.Incompatible<*>> {
it.reason ?: "<unknown>"
}
val SYMBOL_OWNER_DECLARATION_FQ_NAME = Renderer<IrSymbol> {
(it.owner as? IrDeclarationWithName)?.fqNameWhenAvailable?.asString() ?: "unknown name"
}
}
@@ -33,6 +33,7 @@ object IrActualizer {
typeSystemContext,
ktDiagnosticReporter
).collect()
IrExpectActualAnnotationMatchingChecker(expectActualMap, actualDeclarations, typeSystemContext, ktDiagnosticReporter).check()
// 2. Remove top-only expect declarations since they are not needed anymore and should not be presented in the final IrFragment
// Expect fake-overrides from non-expect classes remain untouched since they will be actualized in the next phase.
@@ -122,6 +122,18 @@ internal fun KtDiagnosticReporterWithImplicitIrBasedContext.reportIncompatibleEx
)
}
internal fun KtDiagnosticReporterWithImplicitIrBasedContext.reportActualAnnotationsNotMatchExpect(
expectSymbol: IrSymbol,
actualSymbol: IrSymbol,
reportOn: IrSymbol,
) {
at(reportOn.owner as IrDeclaration).report(
CommonBackendErrors.ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT,
expectSymbol,
actualSymbol,
)
}
internal fun IrElement.containsOptionalExpectation(): Boolean {
return this is IrClass &&
this.kind == ClassKind.ANNOTATION_CLASS &&
@@ -0,0 +1,55 @@
/*
* 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.backend.common.actualizer
import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeAliasSymbol
import org.jetbrains.kotlin.ir.types.IrTypeSystemContext
import org.jetbrains.kotlin.ir.util.classIdOrFail
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualAnnotationMatchChecker
internal class IrExpectActualAnnotationMatchingChecker(
private val matchedExpectToActual: Map<IrSymbol, IrSymbol>,
private val classActualizationInfo: ClassActualizationInfo,
typeSystemContext: IrTypeSystemContext,
private val diagnosticsReporter: KtDiagnosticReporterWithImplicitIrBasedContext,
) {
private val context = object : IrExpectActualMatchingContext(typeSystemContext, classActualizationInfo.actualClasses) {
override fun onMatchedClasses(expectClassSymbol: IrClassSymbol, actualClassSymbol: IrClassSymbol) {
error("Must not be called")
}
override fun onMatchedCallables(expectSymbol: IrSymbol, actualSymbol: IrSymbol) {
error("Must not be called")
}
}
fun check() {
for ((expectSymbol, actualSymbol) in matchedExpectToActual.entries) {
val incompatibility =
AbstractExpectActualAnnotationMatchChecker.areAnnotationsCompatible(expectSymbol, actualSymbol, context) ?: continue
val reportOn = getTypealiasSymbolIfActualizedViaTypealias(expectSymbol) ?: actualSymbol
diagnosticsReporter.reportActualAnnotationsNotMatchExpect(
incompatibility.expectSymbol as IrSymbol,
incompatibility.actualSymbol as IrSymbol,
reportOn,
)
}
}
private fun getTypealiasSymbolIfActualizedViaTypealias(expectSymbol: IrSymbol): IrTypeAliasSymbol? {
val expectDeclaration = expectSymbol.owner as IrDeclaration
val topLevelExpectClass = expectDeclaration.parentsWithSelf.filterIsInstance<IrClass>().lastOrNull() ?: return null
val classId = topLevelExpectClass.classIdOrFail
return classActualizationInfo.actualTypeAliases[classId]
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext
import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext.AnnotationCallInfo
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.types.Variance
@@ -446,12 +448,21 @@ internal abstract class IrExpectActualMatchingContext(
abstract fun onMatchedClasses(expectClassSymbol: IrClassSymbol, actualClassSymbol: IrClassSymbol)
abstract fun onMatchedCallables(expectSymbol: IrSymbol, actualSymbol: IrSymbol)
override val DeclarationSymbolMarker.annotations: List<ExpectActualMatchingContext.AnnotationCallInfo>
// TODO(Roman.Efremov): implement in subsequent commits
get() = emptyList()
override val DeclarationSymbolMarker.annotations: List<AnnotationCallInfo>
get() = asIr().annotations.map(::AnnotationCallInfoImpl)
override fun areAnnotationArgumentsEqual(annotation1: ExpectActualMatchingContext.AnnotationCallInfo, annotation2: ExpectActualMatchingContext.AnnotationCallInfo): Boolean {
// TODO(Roman.Efremov): implement in subsequent commits
return true
override fun areAnnotationArgumentsEqual(annotation1: AnnotationCallInfo, annotation2: AnnotationCallInfo): Boolean {
fun AnnotationCallInfo.getIrElement(): IrConstructorCall = (this as AnnotationCallInfoImpl).irElement
return areIrExpressionConstValuesEqual(annotation1.getIrElement(), annotation2.getIrElement())
}
internal fun getClassIdAfterActualization(classId: ClassId): ClassId {
return expectToActualClassMap[classId]?.classId ?: classId
}
private class AnnotationCallInfoImpl(val irElement: IrConstructorCall) : AnnotationCallInfo {
override val classId: ClassId?
get() = irElement.type.getClass()?.classId
}
}
@@ -0,0 +1,45 @@
/*
* 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.backend.common.actualizer
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.classOrFail
internal fun IrExpectActualMatchingContext.areIrExpressionConstValuesEqual(a: IrElement?, b: IrElement?): Boolean {
return when {
a == null || b == null -> (a == null) == (b == null)
a::class != b::class -> false
a is IrConst<*> && b is IrConst<*> -> a.value == b.value
a is IrClassReference && b is IrClassReference -> equalBy(a, b) {
val classId = it.classType.classOrFail.classId
getClassIdAfterActualization(classId)
}
a is IrGetEnumValue && b is IrGetEnumValue -> equalBy(a, b) { it.symbol.signature?.toString() }
a is IrVararg && b is IrVararg -> {
equalBy(a, b) { it.elements.size } &&
a.elements.zip(b.elements).all { (f, s) -> areIrExpressionConstValuesEqual(f, s) }
}
a is IrConstructorCall && b is IrConstructorCall -> {
equalBy(a, b) { it.valueArgumentsCount } &&
areCompatibleExpectActualTypes(a.type, b.type) &&
(0..<a.valueArgumentsCount).all { i ->
areIrExpressionConstValuesEqual(a.getValueArgument(i), b.getValueArgument(i))
}
}
else -> error("Not handled expression types $a $b")
}
}
private inline fun <T : Any> equalBy(first: T, second: T, selector: (T) -> Any?): Boolean =
selector(first) == selector(second)