[FIR] Fix case with lazy resolve in expect-actual annotation checker

Unresolved annotation arguments were treated as absent arguments,
which lead to false-positive reports.
Add assert and test for that and fix.

MR: KT-MR-12245

^KT-60671 Fixed
This commit is contained in:
Roman Efremov
2023-09-20 11:09:51 +02:00
committed by Space Team
parent 4c75fb108f
commit 0fd700de21
10 changed files with 106 additions and 13 deletions
@@ -11,14 +11,13 @@ import org.jetbrains.kotlin.descriptors.Visibility
import org.jetbrains.kotlin.fir.*
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.utils.isActual
import org.jetbrains.kotlin.fir.expressions.FirAnnotation
import org.jetbrains.kotlin.fir.expressions.FirConstExpression
import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.symbols.resolvedAnnotationsWithArguments
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.mpp.*
import org.jetbrains.kotlin.name.CallableId
@@ -364,6 +363,13 @@ class FirExpectActualMatchingContextImpl private constructor(
}
private fun areFirAnnotationsEqual(annotation1: FirAnnotation, annotation2: FirAnnotation): Boolean {
fun FirAnnotation.hasResolvedArguments(): Boolean {
return resolved || (this is FirAnnotationCall && arguments.isEmpty())
}
check(annotation1.hasResolvedArguments() && annotation2.hasResolvedArguments()) {
"By this time compared annotations are expected to have resolved arguments"
}
if (!areCompatibleExpectActualTypes(annotation1.resolvedType, annotation2.resolvedType)) {
return false
}
@@ -503,25 +509,37 @@ class FirExpectActualMatchingContextImpl private constructor(
override fun TypeRefMarker.getClassId(): ClassId? = (this as FirResolvedTypeRef).type.fullyExpandedType(actualSession).classId
override fun checkAnnotationsOnTypeRefAndArguments(
expectContainingSymbol: DeclarationSymbolMarker,
actualContainingSymbol: DeclarationSymbolMarker,
expectTypeRef: TypeRefMarker,
actualTypeRef: TypeRefMarker,
checker: ExpectActualMatchingContext.AnnotationsCheckerCallback,
) {
check(expectTypeRef is FirResolvedTypeRef && actualTypeRef is FirResolvedTypeRef)
checkAnnotationsOnTypeRefAndArgumentsImpl(expectTypeRef, actualTypeRef, checker)
checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol.asSymbol(), actualContainingSymbol.asSymbol(),
expectTypeRef, actualTypeRef, checker
)
}
private fun checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol: FirBasedSymbol<*>,
actualContainingSymbol: FirBasedSymbol<*>,
expectTypeRef: FirTypeRef?,
actualTypeRef: FirTypeRef?,
checker: ExpectActualMatchingContext.AnnotationsCheckerCallback,
) {
fun FirAnnotationContainer.getAnnotations() = annotations.map(::AnnotationCallInfoImpl)
fun FirAnnotationContainer.getAnnotations(anchor: FirBasedSymbol<*>): List<AnnotationCallInfoImpl> {
return resolvedAnnotationsWithArguments(anchor).map(::AnnotationCallInfoImpl)
}
if (expectTypeRef == null || actualTypeRef == null) return
if (expectTypeRef is FirErrorTypeRef || actualTypeRef is FirErrorTypeRef) return
checker.check(expectTypeRef.getAnnotations(), actualTypeRef.getAnnotations(), FirSourceElement(actualTypeRef.source))
checker.check(
expectTypeRef.getAnnotations(expectContainingSymbol), actualTypeRef.getAnnotations(actualContainingSymbol),
FirSourceElement(actualTypeRef.source)
)
val expectDelegatedTypeRef = (expectTypeRef as? FirResolvedTypeRef)?.delegatedTypeRef ?: return
val actualDelegatedTypeRef = (actualTypeRef as? FirResolvedTypeRef?)?.delegatedTypeRef ?: return
@@ -538,22 +556,30 @@ class FirExpectActualMatchingContextImpl private constructor(
if (expectTypeArgument !is FirTypeProjectionWithVariance || actualTypeArgument !is FirTypeProjectionWithVariance) {
continue
}
checkAnnotationsOnTypeRefAndArgumentsImpl(expectTypeArgument.typeRef, actualTypeArgument.typeRef, checker)
checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol, actualContainingSymbol,
expectTypeArgument.typeRef, actualTypeArgument.typeRef, checker
)
}
}
}
expectDelegatedTypeRef is FirFunctionTypeRef && actualDelegatedTypeRef is FirFunctionTypeRef -> {
checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol, actualContainingSymbol,
expectDelegatedTypeRef.receiverTypeRef, actualDelegatedTypeRef.receiverTypeRef, checker,
)
checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol, actualContainingSymbol,
expectDelegatedTypeRef.returnTypeRef, actualDelegatedTypeRef.returnTypeRef, checker,
)
val expectParams = expectDelegatedTypeRef.parameters
val actualParams = actualDelegatedTypeRef.parameters
for ((expectParam, actualParam) in expectParams.zipIfSizesAreEqual(actualParams).orEmpty()) {
checkAnnotationsOnTypeRefAndArgumentsImpl(expectParam.returnTypeRef, actualParam.returnTypeRef, checker)
checkAnnotationsOnTypeRefAndArgumentsImpl(
expectContainingSymbol, actualContainingSymbol,
expectParam.returnTypeRef, actualParam.returnTypeRef, checker
)
}
}
}