[FIR] Support several super-related diagnostics
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.fir.analysis.checkers
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.types.ConeClassLikeType
|
||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeRef
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
/**
|
||||
* Returns true if this is a superclass of other.
|
||||
*/
|
||||
fun FirClass<*>.isSuperclassOf(other: FirClass<*>): Boolean {
|
||||
/**
|
||||
* Hides additional parameters.
|
||||
*/
|
||||
fun FirClass<*>.isSuperclassOf(other: FirClass<*>, exclude: MutableSet<FirClass<*>>): Boolean {
|
||||
for (it in other.superTypeRefs) {
|
||||
val that = it.firClassLike(session) as? FirClass<*>
|
||||
?: continue
|
||||
|
||||
if (that in exclude) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (that.classKind == ClassKind.CLASS) {
|
||||
if (that == this) {
|
||||
return true
|
||||
}
|
||||
|
||||
exclude.add(that)
|
||||
return this.isSuperclassOf(that, exclude)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return isSuperclassOf(other, mutableSetOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this is a superclass of other.
|
||||
*/
|
||||
fun FirClass<*>.isSupertypeOf(other: FirClass<*>): Boolean {
|
||||
/**
|
||||
* Hides additional parameters.
|
||||
*/
|
||||
fun FirClass<*>.isSupertypeOf(other: FirClass<*>, exclude: MutableSet<FirClass<*>>): Boolean {
|
||||
for (it in other.superTypeRefs) {
|
||||
val that = it.firClassLike(session) as? FirClass<*>
|
||||
?: continue
|
||||
|
||||
if (that in exclude) {
|
||||
continue
|
||||
}
|
||||
|
||||
exclude.add(that)
|
||||
|
||||
if (that == this) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (this.isSupertypeOf(that, exclude)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return isSupertypeOf(other, mutableSetOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FirRegularClass associated with this
|
||||
* or null of something goes wrong.
|
||||
*/
|
||||
fun ConeClassLikeType.toRegularClass(session: FirSession): FirRegularClass? {
|
||||
return lookupTag.toSymbol(session).safeAs<FirRegularClassSymbol>()?.fir
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FirRegularClass associated with this
|
||||
* or null of something goes wrong.
|
||||
*/
|
||||
fun ConeKotlinType.toRegularClass(session: FirSession): FirRegularClass? {
|
||||
return safeAs<ConeClassLikeType>()?.toRegularClass(session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FirRegularClass associated with this
|
||||
* or null of something goes wrong.
|
||||
*/
|
||||
fun FirTypeRef.toRegularClass(session: FirSession): FirRegularClass? {
|
||||
return safeAs<FirResolvedTypeRef>()?.type?.toRegularClass(session)
|
||||
}
|
||||
+10
-2
@@ -9,6 +9,14 @@ import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
|
||||
object CallCheckers {
|
||||
val EXPRESSIONS: List<FirExpressionChecker<FirExpression>> = listOf()
|
||||
val QUALIFIED_ACCESS: List<FirQualifiedAccessChecker> = listOf<FirQualifiedAccessChecker>() + EXPRESSIONS
|
||||
val FUNCTION_CALLS: List<FirFunctionCallChecker> = listOf<FirFunctionCallChecker>() + QUALIFIED_ACCESS
|
||||
|
||||
val QUALIFIED_ACCESS: List<FirQualifiedAccessChecker> = listOf(
|
||||
FirSuperNotAvailableChecker,
|
||||
FirNotASupertypeChecker,
|
||||
FirSuperclassNotAccessibleFromInterfaceChecker
|
||||
) + EXPRESSIONS
|
||||
|
||||
val FUNCTION_CALLS: List<FirFunctionCallChecker> = listOf<FirFunctionCallChecker>(
|
||||
|
||||
) + QUALIFIED_ACCESS
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.fir.analysis.checkers.call
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSupertypeOf
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.toRegularClass
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousObject
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClass
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.references.FirSuperReference
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
object FirNotASupertypeChecker : FirQualifiedAccessChecker() {
|
||||
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val surrounding = context.findClosestClass() ?: return
|
||||
|
||||
val targetClass = functionCall.calleeReference.safeAs<FirSuperReference>()
|
||||
?.superTypeRef
|
||||
?.toRegularClass(context.session)
|
||||
?: return
|
||||
|
||||
if (!targetClass.isSupertypeOf(surrounding)) {
|
||||
reporter.report(functionCall.source)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the closest to the end of context.containingDeclarations
|
||||
* item like FirRegularClass or FirAnonymousObject
|
||||
* or null if no such item could be found.
|
||||
*/
|
||||
private fun CheckerContext.findClosestClass(): FirClass<*>? {
|
||||
for (it in containingDeclarations.reversed()) {
|
||||
if (
|
||||
it is FirRegularClass ||
|
||||
it is FirAnonymousObject
|
||||
) {
|
||||
return it as FirClass<*>
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.report(source: FirSourceElement?) {
|
||||
source?.let {
|
||||
report(FirErrors.NOT_A_SUPERTYPE.on(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.fir.analysis.checkers.call
|
||||
|
||||
import org.jetbrains.kotlin.KtNodeTypes.*
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
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.expressions.FirQualifiedAccessExpression
|
||||
|
||||
object FirSuperNotAvailableChecker : FirQualifiedAccessChecker() {
|
||||
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (functionCall.source?.elementType == SUPER_EXPRESSION) {
|
||||
val isInsideClass = context.containingDeclarations.any {
|
||||
it.source?.elementType == CLASS || it.source?.elementType == OBJECT_LITERAL
|
||||
}
|
||||
|
||||
if (!isInsideClass) {
|
||||
reporter.report(functionCall.source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.report(source: FirSourceElement?) {
|
||||
source?.let {
|
||||
report(FirErrors.SUPER_NOT_AVAILABLE.on(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.fir.analysis.checkers.call
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isSuperclassOf
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.declarations.FirClassLikeDeclaration
|
||||
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.firSymbolProvider
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
object FirSuperclassNotAccessibleFromInterfaceChecker : FirQualifiedAccessChecker() {
|
||||
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val closestClass = context.findClosest<FirRegularClass>() ?: return
|
||||
|
||||
if (closestClass.classKind == ClassKind.INTERFACE) {
|
||||
val origin = getClassLikeDeclaration(functionCall, context)
|
||||
?.symbol.safeAs<FirRegularClassSymbol>()
|
||||
?.fir
|
||||
?: return
|
||||
|
||||
if (origin.source != null && origin.isSuperclassOf(closestClass)) {
|
||||
reporter.report(functionCall.explicitReceiver?.source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ClassLikeDeclaration where the function has been defined
|
||||
* or null if no proper declaration has been found.
|
||||
*/
|
||||
private fun getClassLikeDeclaration(functionCall: FirQualifiedAccessExpression, context: CheckerContext): FirClassLikeDeclaration<*>? {
|
||||
val classId = functionCall.calleeReference.safeAs<FirResolvedNamedReference>()
|
||||
?.resolvedSymbol.safeAs<FirNamedFunctionSymbol>()
|
||||
?.callableId
|
||||
?.classId
|
||||
?: return null
|
||||
|
||||
if (!classId.isLocal) {
|
||||
return context.session.firSymbolProvider.getClassLikeSymbolByFqName(classId)?.fir
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun DiagnosticReporter.report(source: FirSourceElement?) {
|
||||
source?.let {
|
||||
report(FirErrors.SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE.on(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-6
@@ -7,36 +7,58 @@ package org.jetbrains.kotlin.fir.analysis.checkers.context
|
||||
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
|
||||
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
|
||||
import org.jetbrains.kotlin.fir.resolve.SessionHolder
|
||||
import org.jetbrains.kotlin.fir.resolve.calls.ImplicitReceiverValue
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
abstract class CheckerContext {
|
||||
abstract val implicitReceiverStack: ImplicitReceiverStack
|
||||
abstract val containingDeclarations: List<FirDeclaration>
|
||||
abstract val sessionHolder: SessionHolder
|
||||
|
||||
val session: FirSession
|
||||
get() = sessionHolder.session
|
||||
|
||||
/**
|
||||
* Returns the closest to the end of context.containingDeclarations
|
||||
* T instance or null if no such item could be found.
|
||||
*/
|
||||
inline fun <reified T : FirDeclaration> findClosest(): T? {
|
||||
for (it in containingDeclarations.reversed()) {
|
||||
return it as? T ?: continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class PersistentCheckerContext(
|
||||
override val implicitReceiverStack: PersistentImplicitReceiverStack,
|
||||
override val containingDeclarations: PersistentList<FirDeclaration>
|
||||
override val implicitReceiverStack: PersistentImplicitReceiverStack = PersistentImplicitReceiverStack(),
|
||||
override val containingDeclarations: PersistentList<FirDeclaration> = persistentListOf(),
|
||||
override val sessionHolder: SessionHolder
|
||||
) : CheckerContext() {
|
||||
constructor() : this(
|
||||
constructor(sessionHolder: SessionHolder) : this(
|
||||
PersistentImplicitReceiverStack(),
|
||||
persistentListOf()
|
||||
persistentListOf(),
|
||||
sessionHolder
|
||||
)
|
||||
|
||||
fun addImplicitReceiver(name: Name?, value: ImplicitReceiverValue<*>): PersistentCheckerContext {
|
||||
return PersistentCheckerContext(
|
||||
implicitReceiverStack.add(name, value),
|
||||
containingDeclarations
|
||||
containingDeclarations,
|
||||
sessionHolder
|
||||
)
|
||||
}
|
||||
|
||||
fun addDeclaration(declaration: FirDeclaration): PersistentCheckerContext {
|
||||
return PersistentCheckerContext(
|
||||
implicitReceiverStack,
|
||||
containingDeclarations.add(declaration)
|
||||
containingDeclarations.add(declaration),
|
||||
sessionHolder
|
||||
)
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -48,7 +48,8 @@ abstract class AbstractDiagnosticCollector(
|
||||
private var componentsInitialized = false
|
||||
private val visitor = Visitor()
|
||||
|
||||
private var context = PersistentCheckerContext()
|
||||
@Suppress("LeakingThis")
|
||||
private var context = PersistentCheckerContext(this)
|
||||
|
||||
fun initializeComponents(vararg components: AbstractDiagnosticCollectorComponent) {
|
||||
if (componentsInitialized) {
|
||||
|
||||
+4
@@ -57,6 +57,10 @@ class DeclarationCheckersDiagnosticComponent(collector: AbstractDiagnosticCollec
|
||||
runCheck { DeclarationCheckers.DECLARATIONS.check(enumEntry, data, it) }
|
||||
}
|
||||
|
||||
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: CheckerContext) {
|
||||
runCheck { DeclarationCheckers.DECLARATIONS.check(anonymousObject, data, it) }
|
||||
}
|
||||
|
||||
private fun <D : FirDeclaration> List<FirDeclarationChecker<D>>.check(
|
||||
declaration: D,
|
||||
context: CheckerContext,
|
||||
|
||||
@@ -37,6 +37,9 @@ object FirErrors {
|
||||
val VARIABLE_EXPECTED by error0<FirSourceElement, PsiElement>()
|
||||
val RETURN_NOT_ALLOWED by error0<FirSourceElement, PsiElement>()
|
||||
val SUPER_IS_NOT_AN_EXPRESSION by error0<FirSourceElement, PsiElement>()
|
||||
val SUPER_NOT_AVAILABLE by error0<FirSourceElement, PsiElement>()
|
||||
val NOT_A_SUPERTYPE by error0<FirSourceElement, PsiElement>()
|
||||
val SUPERCLASS_NOT_ACCESSIBLE_FROM_INTERFACE by error0<FirSourceElement, PsiElement>()
|
||||
|
||||
val INAPPLICABLE_INFIX_MODIFIER by existing<FirSourceElement, PsiElement, String>(Errors.INAPPLICABLE_INFIX_MODIFIER)
|
||||
val CONSTRUCTOR_IN_OBJECT by existing<FirSourceElement, KtDeclaration>(Errors.CONSTRUCTOR_IN_OBJECT)
|
||||
|
||||
Reference in New Issue
Block a user