[IR] Implement IR checker for expect actual annotations matching
^KT-58551
This commit is contained in:
committed by
Space Team
parent
b6cae1adcc
commit
2980179bd7
+16
-1
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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.
|
||||
|
||||
+12
@@ -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 &&
|
||||
|
||||
+55
@@ -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]
|
||||
}
|
||||
}
|
||||
+17
-6
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -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)
|
||||
Reference in New Issue
Block a user