[FIR] Fix annotations invalid references diagnostics

This commit is contained in:
Igor Yakovlev
2021-04-30 17:12:58 +02:00
committed by TeamCityServer
parent 261098a122
commit ae63ad0b6d
18 changed files with 78 additions and 39 deletions
@@ -5,7 +5,7 @@ interface I {
}
class A {
fun too(): <!NOT_AN_ANNOTATION_CLASS!>@Annotation<!> Unit {}
fun too(): @<!NOT_AN_ANNOTATION_CLASS!>Annotation<!> Unit {}
fun foo(): <!REDUNDANT_RETURN_UNIT_TYPE!>Unit<!>
{
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.transformers.ReturnTypeCalculator
@@ -23,7 +24,7 @@ abstract class CheckerContext {
// Context
abstract val implicitReceiverStack: ImplicitReceiverStack
abstract val containingDeclarations: List<FirDeclaration>
abstract val qualifiedAccesses: List<FirQualifiedAccess>
abstract val qualifiedAccessOrAnnotationCalls: List<FirStatement>
abstract val getClassCalls: List<FirGetClassCall>
// Suppress
@@ -11,7 +11,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirStatement
import org.jetbrains.kotlin.fir.resolve.PersistentImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.SessionHolder
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.name.Name
class PersistentCheckerContext private constructor(
override val implicitReceiverStack: PersistentImplicitReceiverStack,
override val containingDeclarations: PersistentList<FirDeclaration>,
override val qualifiedAccesses: PersistentList<FirQualifiedAccess>,
override val qualifiedAccessOrAnnotationCalls: PersistentList<FirStatement>,
override val getClassCalls: PersistentList<FirGetClassCall>,
override val sessionHolder: SessionHolder,
override val returnTypeCalculator: ReturnTypeCalculator,
@@ -47,7 +47,7 @@ class PersistentCheckerContext private constructor(
return PersistentCheckerContext(
implicitReceiverStack.add(name, value),
containingDeclarations,
qualifiedAccesses,
qualifiedAccessOrAnnotationCalls,
getClassCalls,
sessionHolder,
returnTypeCalculator,
@@ -62,7 +62,7 @@ class PersistentCheckerContext private constructor(
return PersistentCheckerContext(
implicitReceiverStack,
containingDeclarations.add(declaration),
qualifiedAccesses,
qualifiedAccessOrAnnotationCalls,
getClassCalls,
sessionHolder,
returnTypeCalculator,
@@ -73,11 +73,11 @@ class PersistentCheckerContext private constructor(
)
}
fun addQualifiedAccess(qualifiedAccess: FirQualifiedAccess): PersistentCheckerContext {
fun addQualifiedAccessOrAnnotationCall(qualifiedAccessOrAnnotationCall: FirStatement): PersistentCheckerContext {
return PersistentCheckerContext(
implicitReceiverStack,
containingDeclarations,
qualifiedAccesses.add(qualifiedAccess),
this.qualifiedAccessOrAnnotationCalls.add(qualifiedAccessOrAnnotationCall),
getClassCalls,
sessionHolder,
returnTypeCalculator,
@@ -92,7 +92,7 @@ class PersistentCheckerContext private constructor(
return PersistentCheckerContext(
implicitReceiverStack,
containingDeclarations,
qualifiedAccesses,
qualifiedAccessOrAnnotationCalls,
getClassCalls.add(getClassCall),
sessionHolder,
returnTypeCalculator,
@@ -113,7 +113,7 @@ class PersistentCheckerContext private constructor(
return PersistentCheckerContext(
implicitReceiverStack,
containingDeclarations,
qualifiedAccesses,
qualifiedAccessOrAnnotationCalls,
getClassCalls,
sessionHolder,
returnTypeCalculator,
@@ -184,8 +184,8 @@ object FirInlineDeclarationChecker : FirMemberDeclarationChecker() {
private fun FirQualifiedAccess.partOfCall(context: CheckerContext): Boolean {
if (this !is FirExpression) return false
val containingQualifiedAccess = context.qualifiedAccesses.getOrNull(context.qualifiedAccesses.size - 2) ?: return false
if (this == containingQualifiedAccess.explicitReceiver) return true
val containingQualifiedAccess = context.qualifiedAccessOrAnnotationCalls.getOrNull(context.qualifiedAccessOrAnnotationCalls.size - 2) ?: return false
if (this == (containingQualifiedAccess as? FirQualifiedAccess)?.explicitReceiver) return true
val call = containingQualifiedAccess as? FirCall ?: return false
return call.arguments.any { it.unwrapArgument() == this }
}
@@ -10,13 +10,14 @@ import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.isUnit
object FirStandaloneQualifierChecker : FirResolvedQualifierChecker() {
override fun check(expression: FirResolvedQualifier, context: CheckerContext, reporter: DiagnosticReporter) {
val lastQualifiedAccess = context.qualifiedAccesses.lastOrNull()
val lastQualifiedAccess = context.qualifiedAccessOrAnnotationCalls.lastOrNull() as? FirQualifiedAccess
if (lastQualifiedAccess?.explicitReceiver === expression) return
val lastGetClass = context.getClassCalls.lastOrNull()
if (lastGetClass?.argument === expression) return
@@ -30,7 +30,7 @@ object FirTypeParameterInQualifiedAccessChecker : FirQualifiedAccessChecker() {
// Make sure the current qualified access is not part of another qualified access or class literals.
// E.g., for `T::toString`, which is a callable reference (a subtype of qualified access), type parameter T is checked once as an
// explicit receiver (or LHS). When we visit `T` (as a qualified access), we should not regard it as an expression here.
if (context.qualifiedAccesses.size > 1 || context.getClassCalls.isNotEmpty()) return
if (context.qualifiedAccessOrAnnotationCalls.size > 1 || context.getClassCalls.isNotEmpty()) return
val diagnostic = expression.typeRef.coneTypeParameterInQualifiedAccess ?: return
val source = expression.source ?: return
@@ -175,15 +175,19 @@ abstract class AbstractDiagnosticCollectorVisitor(
}
override fun visitFunctionCall(functionCall: FirFunctionCall, data: Nothing?) {
visitWithQualifiedAccess(functionCall)
visitWithQualifiedAccessOrAnnotationCall(functionCall)
}
override fun visitQualifiedAccessExpression(qualifiedAccessExpression: FirQualifiedAccessExpression, data: Nothing?) {
visitWithQualifiedAccess(qualifiedAccessExpression)
visitWithQualifiedAccessOrAnnotationCall(qualifiedAccessExpression)
}
override fun visitAnnotationCall(annotationCall: FirAnnotationCall, data: Nothing?) {
visitWithQualifiedAccessOrAnnotationCall(annotationCall)
}
override fun visitVariableAssignment(variableAssignment: FirVariableAssignment, data: Nothing?) {
visitWithQualifiedAccess(variableAssignment)
visitWithQualifiedAccessOrAnnotationCall(variableAssignment)
}
override fun visitGetClassCall(getClassCall: FirGetClassCall, data: Nothing?) {
@@ -215,9 +219,9 @@ abstract class AbstractDiagnosticCollectorVisitor(
}
}
private fun visitWithQualifiedAccess(qualifiedAccess: FirQualifiedAccess) {
return withQualifiedAccess(qualifiedAccess) {
visitElement(qualifiedAccess, null)
private fun visitWithQualifiedAccessOrAnnotationCall(qualifiedAccessOrAnnotationCall: FirStatement) {
return withQualifiedAccessOrAnnotationCall(qualifiedAccessOrAnnotationCall) {
visitElement(qualifiedAccessOrAnnotationCall, null)
}
}
@@ -228,9 +232,9 @@ abstract class AbstractDiagnosticCollectorVisitor(
}
@OptIn(PrivateForInline::class)
inline fun <R> withQualifiedAccess(qualifiedAccess: FirQualifiedAccess, block: () -> R): R {
inline fun <R> withQualifiedAccessOrAnnotationCall(qualifiedAccessOrAnnotationCall: FirStatement, block: () -> R): R {
val existingContext = context
context = context.addQualifiedAccess(qualifiedAccess)
context = context.addQualifiedAccessOrAnnotationCall(qualifiedAccessOrAnnotationCall)
try {
return block()
} finally {
@@ -46,9 +46,13 @@ class ErrorNodeDiagnosticCollectorComponent(
override fun visitErrorNamedReference(errorNamedReference: FirErrorNamedReference, data: CheckerContext) {
val source = errorNamedReference.source ?: return
val qualifiedAccess = data.qualifiedAccesses.lastOrNull()?.takeIf {
val qualifiedAccessOrAnnotationCall = data.qualifiedAccessOrAnnotationCalls.lastOrNull()?.takeIf {
// Use the source of the enclosing FirQualifiedAccessExpression if it is exactly the call to the erroneous callee.
it is FirQualifiedAccessExpression && it.calleeReference == errorNamedReference
when (it) {
is FirQualifiedAccessExpression -> it.calleeReference == errorNamedReference
is FirAnnotationCall -> it.calleeReference == errorNamedReference
else -> false
}
}
// Don't report duplicated unresolved reference on annotation entry (already reported on its type)
if (source.elementType == KtNodeTypes.ANNOTATION_ENTRY && errorNamedReference.diagnostic is ConeUnresolvedNameError) return
@@ -58,12 +62,14 @@ class ErrorNodeDiagnosticCollectorComponent(
) return
// If the receiver cannot be resolved, we skip reporting any further problems for this call.
if (qualifiedAccess?.dispatchReceiver.hasUnresolvedNameError() ||
qualifiedAccess?.extensionReceiver.hasUnresolvedNameError() ||
qualifiedAccess?.explicitReceiver.hasUnresolvedNameError()
) return
if (qualifiedAccessOrAnnotationCall is FirQualifiedAccess) {
if (qualifiedAccessOrAnnotationCall.dispatchReceiver.hasUnresolvedNameError() ||
qualifiedAccessOrAnnotationCall.extensionReceiver.hasUnresolvedNameError() ||
qualifiedAccessOrAnnotationCall.explicitReceiver.hasUnresolvedNameError()
) return
}
reportFirDiagnostic(errorNamedReference.diagnostic, source, reporter, data, qualifiedAccess?.source)
reportFirDiagnostic(errorNamedReference.diagnostic, source, reporter, data, qualifiedAccessOrAnnotationCall?.source)
}
private fun FirExpression?.hasUnresolvedNameError(): Boolean {
@@ -336,7 +336,12 @@ class DeclarationsConverter(
useSiteTarget = annotationUseSiteTarget ?: defaultAnnotationUseSiteTarget
annotationTypeRef = constructorCalleePair.first
calleeReference = buildSimpleNamedReference {
source = this@buildAnnotationCall.source
source = unescapedAnnotation
.getChildNodeByType(CONSTRUCTOR_CALLEE)
?.getChildNodeByType(TYPE_REFERENCE)
?.getChildNodeByType(USER_TYPE)
?.getChildNodeByType(REFERENCE_EXPRESSION)
?.toFirSourceElement()
this.name = name
}
extractArgumentsFrom(constructorCalleePair.second, stubMode)
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.fir
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.diagnostics.ConeStubDiagnostic
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.builder.buildResolvedReifiedParameterReference
import org.jetbrains.kotlin.fir.references.*
@@ -410,7 +411,8 @@ class FirCallResolver(
buildErrorReference(
callInfo,
if (annotationClassSymbol != null) ConeIllegalAnnotationError(reference.name)
else ConeUnresolvedNameError(reference.name),
//calleeReference and annotationTypeRef are both error nodes so we need to avoid doubling of the diagnostic report
else ConeStubDiagnostic(ConeUnresolvedNameError(reference.name)),
reference.source
)
}
@@ -1,6 +1,6 @@
// !WITH_NEW_INFERENCE
import java.util.ArrayList
<!NOT_AN_ANNOTATION_CLASS!>@ArrayList<Int>(1, 1)<!> fun b() {}
@<!NOT_AN_ANNOTATION_CLASS!>ArrayList<!><Int>(1, 1) fun b() {}
@<!UNRESOLVED_REFERENCE!>Xoo<!>(<!UNRESOLVED_REFERENCE!>x<!>) fun c() {}
@java.lang.Deprecated(<!TOO_MANY_ARGUMENTS, UNRESOLVED_REFERENCE!>x<!>) fun a() {}
@@ -0,0 +1,19 @@
// FILE: a.kt
annotation class annotation
// FILE: test/b.kt
package test
@test.<!NOT_AN_ANNOTATION_CLASS!>annotation<!> class annotation
// FILE: other/c.kt
package other
annotation class My
@test.<!NOT_AN_ANNOTATION_CLASS!>annotation<!> class Your
@My class Our
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// FILE: a.kt
annotation class annotation
@@ -17,4 +16,4 @@ annotation class My
<!NOT_AN_ANNOTATION_CLASS!>@test.annotation<!> class Your
@My class Our
@My class Our
@@ -0,0 +1,3 @@
class Foo
@<!NOT_AN_ANNOTATION_CLASS!>Foo<!> class Bar
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
class Foo
<!NOT_AN_ANNOTATION_CLASS!>@Foo<!> class Bar
<!NOT_AN_ANNOTATION_CLASS!>@Foo<!> class Bar
@@ -1,2 +1,2 @@
// Class constructor parameter CAN be recursively annotated
class RecursivelyAnnotated(<!NOT_AN_ANNOTATION_CLASS, NOT_AN_ANNOTATION_CLASS!>@RecursivelyAnnotated(1)<!> val x: Int)
class RecursivelyAnnotated(@<!NOT_AN_ANNOTATION_CLASS, NOT_AN_ANNOTATION_CLASS!>RecursivelyAnnotated<!>(1) val x: Int)
@@ -1,7 +1,7 @@
// !CHECK_TYPE
@<!INVISIBLE_REFERENCE!>kotlin.internal.InlineOnly<!>
@kotlin.internal.<!INVISIBLE_REFERENCE!>InlineOnly<!>
public inline fun <C, R> C.ifEmpty(f: () -> R): R where C : Collection<*>, C : R = if (isEmpty()) f() else this
public fun <T> listOf(t: T): List<T> = TODO()
@@ -2,5 +2,5 @@
package a.b
@<error descr="[INVISIBLE_REFERENCE] Symbol kotlin/internal/InlineOnly.InlineOnly is invisible">kotlin.internal.InlineOnly</error>
@kotlin.internal.<error descr="[INVISIBLE_REFERENCE] Symbol kotlin/internal/InlineOnly.InlineOnly is invisible">InlineOnly</error>
inline fun foo() {}