[FIR] Add diagnostic when annotation argument is resolved ambiguously

Annotation arguments that are resolved in COMPILER_REQUIRED_ANNOTATIONS
phase are resolved again in ANNOTATION_ARGUMENTS phase. If they resolve
to a different symbol, report an error.

KT-56177
This commit is contained in:
Kirill Rakhman
2023-02-23 18:16:39 +01:00
committed by Space Team
parent 9dda5e4fcd
commit 2139914061
27 changed files with 253 additions and 5 deletions
@@ -35874,6 +35874,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.(fir|ll)\\.kts?$"), true);
}
@Test
@TestMetadata("annotationTargetResolvedAmbiguously.kt")
public void testAnnotationTargetResolvedAmbiguously() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationTargetResolvedAmbiguously.kt");
}
@Test
@TestMetadata("annotationsTargetingLateinitAccessors.kt")
public void testAnnotationsTargetingLateinitAccessors() throws Exception {
@@ -35970,6 +35970,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile("^(.+)\\.(fir|ll)\\.kts?$"), true);
}
@Test
@TestMetadata("annotationTargetResolvedAmbiguously.kt")
public void testAnnotationTargetResolvedAmbiguously() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/annotations/annotationTargetResolvedAmbiguously.kt");
}
@Test
@TestMetadata("annotationsTargetingLateinitAccessors.kt")
public void testAnnotationsTargetingLateinitAccessors() throws Exception {
@@ -309,10 +309,14 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
val WRONG_EXTENSION_FUNCTION_TYPE_WARNING by warning<KtAnnotationEntry>()
val ANNOTATION_IN_WHERE_CLAUSE_ERROR by error<KtAnnotationEntry>()
val PLUGIN_ANNOTATION_AMBIGUITY by error<PsiElement>() {
val PLUGIN_ANNOTATION_AMBIGUITY by error<PsiElement> {
parameter<ConeKotlinType>("typeFromCompilerPhase")
parameter<ConeKotlinType>("typeFromTypesPhase")
}
val AMBIGUOUS_ANNOTATION_ARGUMENT by error<PsiElement> {
parameter<List<FirBasedSymbol<*>>>("symbols")
}
}
val OPT_IN by object : DiagnosticGroup("OptIn") {
@@ -264,6 +264,7 @@ object FirErrors {
val WRONG_EXTENSION_FUNCTION_TYPE_WARNING by warning0<KtAnnotationEntry>()
val ANNOTATION_IN_WHERE_CLAUSE_ERROR by error0<KtAnnotationEntry>()
val PLUGIN_ANNOTATION_AMBIGUITY by error2<PsiElement, ConeKotlinType, ConeKotlinType>()
val AMBIGUOUS_ANNOTATION_ARGUMENT by error1<PsiElement, List<FirBasedSymbol<*>>>()
// OptIn
val OPT_IN_USAGE by warning2<PsiElement, FqName, String>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
@@ -58,6 +58,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_WITHOUT_EXPECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ACTUALS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ALTERED_ASSIGN
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ANNOTATION_ARGUMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ANONYMOUS_TYPE_INFERRED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_CALL_WITH_IMPLICIT_CONTEXT_RECEIVER
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_EXPECTS
@@ -928,10 +929,15 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
)
map.put(
PLUGIN_ANNOTATION_AMBIGUITY,
"Resolution of annotation type is ambigous between {1} and plugin annotation {0}. Please specify fully qualified name of annotation",
"Resolution of annotation type is ambiguous between {1} and plugin annotation {0}. Please specify fully qualified name of annotation",
RENDER_TYPE,
RENDER_TYPE
)
map.put(
AMBIGUOUS_ANNOTATION_ARGUMENT,
"Resolution of annotation argument is ambiguous between {0}. Please specify fully qualified name of argument",
SYMBOLS,
)
// Exposed visibility group // #
map.put(
@@ -132,6 +132,8 @@ private fun ConeDiagnostic.toKtDiagnostic(
is ConeAmbiguouslyResolvedAnnotationFromPlugin -> {
FirErrors.PLUGIN_ANNOTATION_AMBIGUITY.createOn(source, typeFromCompilerPhase, typeFromTypesPhase)
}
is ConeAmbiguouslyResolvedAnnotationArgument ->
FirErrors.AMBIGUOUS_ANNOTATION_ARGUMENT.createOn(source, listOfNotNull(symbolFromCompilerPhase, symbolFromAnnotationArgumentsPhase))
is ConeAmbiguousFunctionTypeKinds -> FirErrors.AMBIGUOUS_FUNCTION_TYPE_KIND.createOn(source, kinds)
else -> throw IllegalArgumentException("Unsupported diagnostic type: ${this.javaClass}")
}
@@ -111,6 +111,7 @@ fun FirResolvedQualifier.continueQualifier(
packageFqName = this@continueQualifier.packageFqName
relativeClassFqName = this@continueQualifier.relativeClassFqName?.child(name)
symbol = nestedClassSymbol as FirClassLikeSymbol<*>
isFullyQualified = true
val outerTypeArguments = this.typeArguments.toList()
this.typeArguments.clear()
@@ -180,6 +181,7 @@ private fun FqName.continueQualifierInPackage(
apiVersion
)
)
isFullyQualified = true
}.apply {
resultType = components.typeForQualifier(this)
}
@@ -139,6 +139,7 @@ internal abstract class AbstractFirSpecificAnnotationResolveTransformer(
relativeClassFqName = symbol.classId.relativeClassName
typeRef = FirImplicitUnitTypeRef(receiver.typeRef.source)
this.symbol = symbol
isFullyQualified = segments.isNotEmpty()
}
// Resolve enum entry by name from the declarations of the receiver.
@@ -7,12 +7,26 @@ package org.jetbrains.kotlin.fir.resolve.transformers.plugin
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.containingClassLookupTag
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildPropertyAccessExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildErrorNamedReference
import org.jetbrains.kotlin.fir.references.builder.buildSimpleNamedReference
import org.jetbrains.kotlin.fir.references.toResolvedBaseSymbol
import org.jetbrains.kotlin.fir.resolve.ResolutionMode
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeAmbiguouslyResolvedAnnotationArgument
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer
import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer
import org.jetbrains.kotlin.fir.symbols.impl.FirEnumEntrySymbol
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.StandardClassIds
open class FirAnnotationArgumentsResolveTransformer(
session: FirSession,
@@ -252,9 +266,74 @@ abstract class AbstractFirExpressionsResolveTransformerForAnnotations(transforme
}
}
/**
* Set of enum class IDs that are resolved in COMPILER_REQUIRED_ANNOTATIONS phase that need to be rechecked here.
*/
private val classIdsToCheck: Set<ClassId> = setOf(StandardClassIds.DeprecationLevel, StandardClassIds.AnnotationTarget)
private class FirExpressionsResolveTransformerForSpecificAnnotations(transformer: FirAbstractBodyResolveTransformerDispatcher) :
AbstractFirExpressionsResolveTransformerForAnnotations(transformer) {
override fun transformQualifiedAccessExpression(
qualifiedAccessExpression: FirQualifiedAccessExpression,
data: ResolutionMode
): FirStatement {
val calleeReference = qualifiedAccessExpression.calleeReference
if (calleeReference is FirResolvedNamedReference &&
calleeReference.resolvedSymbol.let { it is FirEnumEntrySymbol && it.containingClassLookupTag()?.classId in classIdsToCheck } &&
qualifiedAccessExpression is FirPropertyAccessExpression
) {
val symbolFromCompilerPhase = calleeReference.resolvedSymbol
(qualifiedAccessExpression.explicitReceiver as? FirResolvedQualifier)?.let {
qualifiedAccessExpression.replaceResolvedQualifierReceiver(it)
}
qualifiedAccessExpression.replaceDispatchReceiver(FirNoReceiverExpression)
qualifiedAccessExpression.replaceTypeRef(noExpectedType)
qualifiedAccessExpression.replaceCalleeReference(buildSimpleNamedReference {
source = calleeReference.source
name = calleeReference.name
})
val resolved = super.transformQualifiedAccessExpression(qualifiedAccessExpression, data)
if (resolved is FirQualifiedAccessExpression) {
// The initial resolution must have been to an enum entry. Report ambiguity if symbolFromArgumentsPhase is different to
// original symbol including null (meaning we would resolve to something other than an enum entry).
val symbolFromArgumentsPhase = resolved.calleeReference.toResolvedBaseSymbol()
if (symbolFromCompilerPhase != symbolFromArgumentsPhase) {
resolved.replaceCalleeReference(buildErrorNamedReference {
source = resolved.calleeReference.source
diagnostic = ConeAmbiguouslyResolvedAnnotationArgument(symbolFromCompilerPhase, symbolFromArgumentsPhase)
})
}
}
return resolved
}
return super.transformQualifiedAccessExpression(qualifiedAccessExpression, data)
}
private fun FirQualifiedAccessExpression.replaceResolvedQualifierReceiver(receiver: FirResolvedQualifier) {
var lastReceiver = buildPropertyAccessExpression {
source = receiver.source
this.calleeReference = buildSimpleNamedReference {
val classId = receiver.classId ?: return
name = classId.relativeClassName.shortName()
}
}
replaceExplicitReceiver(lastReceiver)
if (receiver.isFullyQualified) {
for (segment in receiver.packageFqName.pathSegments().asReversed()) {
lastReceiver.replaceExplicitReceiver(buildPropertyAccessExpression {
this.calleeReference = buildSimpleNamedReference { name = segment }
}.also { lastReceiver = it })
}
}
}
override fun resolveQualifiedAccessAndSelectCandidate(
qualifiedAccessExpression: FirQualifiedAccessExpression,
isUsedAsReceiver: Boolean,
@@ -251,3 +251,15 @@ class ConeAmbiguouslyResolvedAnnotationFromPlugin(
- types stage: $typeFromTypesPhase
"""
}
class ConeAmbiguouslyResolvedAnnotationArgument(
val symbolFromCompilerPhase: FirBasedSymbol<*>,
val symbolFromAnnotationArgumentsPhase: FirBasedSymbol<*>?
) : ConeDiagnostic {
override val reason: String
get() = """
Annotation symbol resolved differently on compiler annotation and symbols stages:
- compiler annotations: $symbolFromCompilerPhase
- compiler arguments stage: $symbolFromAnnotationArgumentsPhase
"""
}
@@ -31,6 +31,7 @@ abstract class FirErrorResolvedQualifier : FirResolvedQualifier(), FirDiagnostic
abstract override val symbol: FirClassLikeSymbol<*>?
abstract override val isNullableLHSForCallableReference: Boolean
abstract override val resolvedToCompanionObject: Boolean
abstract override val isFullyQualified: Boolean
abstract override val nonFatalDiagnostics: List<ConeDiagnostic>
abstract override val typeArguments: List<FirTypeProjection>
abstract override val diagnostic: ConeDiagnostic
@@ -30,6 +30,7 @@ abstract class FirResolvedQualifier : FirExpression() {
abstract val symbol: FirClassLikeSymbol<*>?
abstract val isNullableLHSForCallableReference: Boolean
abstract val resolvedToCompanionObject: Boolean
abstract val isFullyQualified: Boolean
abstract val nonFatalDiagnostics: List<ConeDiagnostic>
abstract val typeArguments: List<FirTypeProjection>
@@ -35,6 +35,7 @@ interface FirAbstractResolvedQualifierBuilder {
abstract var symbol: FirClassLikeSymbol<*>?
abstract var isNullableLHSForCallableReference: Boolean
abstract var resolvedToCompanionObject: Boolean
abstract var isFullyQualified: Boolean
abstract val nonFatalDiagnostics: MutableList<ConeDiagnostic>
abstract val typeArguments: MutableList<FirTypeProjection>
@@ -39,6 +39,7 @@ class FirErrorResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, Fi
override var relativeClassFqName: FqName? = null
override var symbol: FirClassLikeSymbol<*>? = null
override var isNullableLHSForCallableReference: Boolean = false
override var isFullyQualified: Boolean = false
override val nonFatalDiagnostics: MutableList<ConeDiagnostic> = mutableListOf()
override val typeArguments: MutableList<FirTypeProjection> = mutableListOf()
lateinit var diagnostic: ConeDiagnostic
@@ -51,6 +52,7 @@ class FirErrorResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, Fi
relativeClassFqName,
symbol,
isNullableLHSForCallableReference,
isFullyQualified,
nonFatalDiagnostics.toMutableOrEmpty(),
typeArguments.toMutableOrEmpty(),
diagnostic,
@@ -41,6 +41,7 @@ class FirResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, FirAnno
override var relativeClassFqName: FqName? = null
override var symbol: FirClassLikeSymbol<*>? = null
override var isNullableLHSForCallableReference: Boolean = false
override var isFullyQualified: Boolean = false
override val nonFatalDiagnostics: MutableList<ConeDiagnostic> = mutableListOf()
override val typeArguments: MutableList<FirTypeProjection> = mutableListOf()
@@ -53,6 +54,7 @@ class FirResolvedQualifierBuilder : FirAbstractResolvedQualifierBuilder, FirAnno
relativeClassFqName,
symbol,
isNullableLHSForCallableReference,
isFullyQualified,
nonFatalDiagnostics.toMutableOrEmpty(),
typeArguments.toMutableOrEmpty(),
)
@@ -33,6 +33,7 @@ internal class FirErrorResolvedQualifierImpl(
override val relativeClassFqName: FqName?,
override val symbol: FirClassLikeSymbol<*>?,
override var isNullableLHSForCallableReference: Boolean,
override val isFullyQualified: Boolean,
override var nonFatalDiagnostics: MutableOrEmptyList<ConeDiagnostic>,
override var typeArguments: MutableOrEmptyList<FirTypeProjection>,
override val diagnostic: ConeDiagnostic,
@@ -34,6 +34,7 @@ internal class FirResolvedQualifierImpl(
override var relativeClassFqName: FqName?,
override val symbol: FirClassLikeSymbol<*>?,
override var isNullableLHSForCallableReference: Boolean,
override val isFullyQualified: Boolean,
override var nonFatalDiagnostics: MutableOrEmptyList<ConeDiagnostic>,
override var typeArguments: MutableOrEmptyList<FirTypeProjection>,
) : FirResolvedQualifier() {
@@ -354,12 +354,12 @@ object BuilderConfigurator : AbstractBuilderConfigurator<FirTreeBuilder>(FirTree
builder(resolvedQualifier) {
parents += abstractResolvedQualifierBuilder
defaultFalse("isNullableLHSForCallableReference")
defaultFalse("isNullableLHSForCallableReference", "isFullyQualified")
}
builder(errorResolvedQualifier) {
parents += abstractResolvedQualifierBuilder
defaultFalse("isNullableLHSForCallableReference")
defaultFalse("isNullableLHSForCallableReference", "isFullyQualified")
}
// builder(safeCallExpression) {
@@ -595,6 +595,7 @@ object NodeConfigurator : AbstractFieldConfigurator<FirTreeBuilder>(FirTreeBuild
+field("symbol", classLikeSymbolType, nullable = true)
+booleanField("isNullableLHSForCallableReference", withReplace = true)
+booleanField("resolvedToCompanionObject", withReplace = true)
+booleanField("isFullyQualified")
+fieldList("nonFatalDiagnostics", coneDiagnosticType, useMutableOrEmpty = true)
+typeArguments.withTransform()
}