FIR: introduce not implemented checker
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ abstract class A : C() {
|
||||
}
|
||||
}
|
||||
|
||||
class B : A() {
|
||||
<!ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED!>class B<!> : A() {
|
||||
override fun f() {
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ public class LightMember<D> : Member<D>, Light() {
|
||||
override fun getName(): String = "Light"
|
||||
}
|
||||
|
||||
public class LightClassWrapper : Light(), Klass {
|
||||
public <!ABSTRACT_MEMBER_NOT_IMPLEMENTED!>class LightClassWrapper<!> : Light(), Klass {
|
||||
fun test() = typeParameters.single()
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -291,6 +291,23 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
parameter<Name>("containingClassName")
|
||||
}
|
||||
|
||||
val ABSTRACT_MEMBER_NOT_IMPLEMENTED by error<FirSourceElement, KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FirClass<*>>("classOrObject")
|
||||
parameter<FirCallableDeclaration<*>>("missingDeclaration")
|
||||
}
|
||||
val ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED by error<FirSourceElement, KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FirClass<*>>("classOrObject")
|
||||
parameter<FirCallableDeclaration<*>>("missingDeclaration")
|
||||
}
|
||||
val MANY_IMPL_MEMBER_NOT_IMPLEMENTED by error<FirSourceElement, KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FirClass<*>>("classOrObject")
|
||||
parameter<FirCallableDeclaration<*>>("missingDeclaration")
|
||||
}
|
||||
val MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED by error<FirSourceElement, KtClassOrObject>(PositioningStrategy.DECLARATION_NAME) {
|
||||
parameter<FirClass<*>>("classOrObject")
|
||||
parameter<FirCallableDeclaration<*>>("missingDeclaration")
|
||||
}
|
||||
|
||||
val RETURN_TYPE_MISMATCH_ON_OVERRIDE by error<FirSourceElement, KtNamedDeclaration>(PositioningStrategy.DECLARATION_RETURN_TYPE) {
|
||||
parameter<FirMemberDeclaration>("function")
|
||||
parameter<FirMemberDeclaration>("superFunction")
|
||||
|
||||
@@ -208,6 +208,10 @@ object FirErrors {
|
||||
val CANNOT_WEAKEN_ACCESS_PRIVILEGE by error3<FirSourceElement, KtModifierListOwner, Visibility, FirCallableDeclaration<*>, Name>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||
val CANNOT_CHANGE_ACCESS_PRIVILEGE by error3<FirSourceElement, KtModifierListOwner, Visibility, FirCallableDeclaration<*>, Name>(SourceElementPositioningStrategies.VISIBILITY_MODIFIER)
|
||||
val OVERRIDING_FINAL_MEMBER by error2<FirSourceElement, KtNamedDeclaration, FirCallableDeclaration<*>, Name>(SourceElementPositioningStrategies.OVERRIDE_MODIFIER)
|
||||
val ABSTRACT_MEMBER_NOT_IMPLEMENTED by error2<FirSourceElement, KtClassOrObject, FirClass<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED by error2<FirSourceElement, KtClassOrObject, FirClass<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val MANY_IMPL_MEMBER_NOT_IMPLEMENTED by error2<FirSourceElement, KtClassOrObject, FirClass<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED by error2<FirSourceElement, KtClassOrObject, FirClass<*>, FirCallableDeclaration<*>>(SourceElementPositioningStrategies.DECLARATION_NAME)
|
||||
val RETURN_TYPE_MISMATCH_ON_OVERRIDE by error2<FirSourceElement, KtNamedDeclaration, FirMemberDeclaration, FirMemberDeclaration>(SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE)
|
||||
val PROPERTY_TYPE_MISMATCH_ON_OVERRIDE by error2<FirSourceElement, KtNamedDeclaration, FirMemberDeclaration, FirMemberDeclaration>(SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE)
|
||||
val VAR_TYPE_MISMATCH_ON_OVERRIDE by error2<FirSourceElement, KtNamedDeclaration, FirMemberDeclaration, FirMemberDeclaration>(SourceElementPositioningStrategies.DECLARATION_RETURN_TYPE)
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.declaration
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.HASHCODE_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClass
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.modality
|
||||
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.declarations.*
|
||||
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.isNullableAny
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
object FirNotImplementedOverrideChecker : FirClassChecker() {
|
||||
|
||||
override fun check(declaration: FirClass<*>, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// TODO: kt4763Property: reporting on `object` literal causes invalid error in test...FirDiagnosticHandler
|
||||
if (declaration !is FirRegularClass) return
|
||||
|
||||
val source = declaration.source ?: return
|
||||
if (source.kind is FirFakeSourceElementKind) return
|
||||
val modality = declaration.modality()
|
||||
if (modality == Modality.ABSTRACT || modality == Modality.SEALED) return
|
||||
if (declaration.isExpect) return
|
||||
val classKind = declaration.classKind
|
||||
// TODO: we should check enum entries (probably as anonymous objects, see above)
|
||||
if (classKind == ClassKind.ANNOTATION_CLASS || classKind == ClassKind.ENUM_CLASS) return
|
||||
|
||||
val classScope = declaration.unsubstitutedScope(
|
||||
context.session, context.sessionHolder.scopeSession, withForcedTypeCalculator = false
|
||||
)
|
||||
|
||||
val notImplementedSymbols = mutableListOf<FirCallableSymbol<*>>()
|
||||
val classPackage = declaration.symbol.classId.packageFqName
|
||||
|
||||
fun FirCallableMemberDeclaration<*>.isInvisible(): Boolean {
|
||||
if (visibility == Visibilities.Private ||
|
||||
!visibility.visibleFromPackage(classPackage, symbol.callableId.packageName)
|
||||
) return true
|
||||
if (visibility == Visibilities.Internal &&
|
||||
session !== declaration.session
|
||||
) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun FirCallableMemberDeclaration<*>.shouldBeImplemented(): Boolean {
|
||||
if (!isAbstract) return false
|
||||
val containingClass = getContainingClass(context)
|
||||
if (containingClass === declaration) return false
|
||||
if (containingClass is FirRegularClass && containingClass.isExpect) return false
|
||||
return true
|
||||
}
|
||||
|
||||
for (name in classScope.getCallableNames()) {
|
||||
classScope.processFunctionsByName(name) { namedFunctionSymbol ->
|
||||
val simpleFunction = namedFunctionSymbol.fir
|
||||
if (!simpleFunction.shouldBeImplemented()) return@processFunctionsByName
|
||||
// TODO: private & package-private functions / properties require another diagnostic
|
||||
// (INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER)
|
||||
if (simpleFunction.isInvisible()) return@processFunctionsByName
|
||||
|
||||
if (declaration.isData && simpleFunction.matchesDataClassSyntheticMemberSignatures) return@processFunctionsByName
|
||||
|
||||
// TODO: suspend function overridden by a Java class in the middle is not properly regarded as an override
|
||||
if (simpleFunction.isSuspend) return@processFunctionsByName
|
||||
notImplementedSymbols += namedFunctionSymbol
|
||||
}
|
||||
classScope.processPropertiesByName(name) { propertySymbol ->
|
||||
val property = propertySymbol.fir as? FirProperty ?: return@processPropertiesByName
|
||||
if (!property.shouldBeImplemented()) return@processPropertiesByName
|
||||
if (property.isInvisible()) return@processPropertiesByName
|
||||
|
||||
notImplementedSymbols += propertySymbol
|
||||
}
|
||||
}
|
||||
|
||||
if (notImplementedSymbols.isNotEmpty()) {
|
||||
val notImplemented = notImplementedSymbols.first().fir
|
||||
if (notImplemented.isFromInterface(context)) {
|
||||
reporter.reportOn(source, FirErrors.ABSTRACT_MEMBER_NOT_IMPLEMENTED, declaration, notImplemented, context)
|
||||
} else {
|
||||
reporter.reportOn(source, FirErrors.ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED, declaration, notImplemented, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirCallableDeclaration<*>.isFromInterface(context: CheckerContext): Boolean =
|
||||
(getContainingClass(context) as? FirRegularClass)?.isInterface == true
|
||||
|
||||
private val FirSimpleFunction.matchesDataClassSyntheticMemberSignatures: Boolean
|
||||
get() = (this.name == OperatorNameConventions.EQUALS && matchesEqualsSignature) ||
|
||||
(this.name == HASHCODE_NAME && matchesHashCodeSignature) ||
|
||||
(this.name == OperatorNameConventions.TO_STRING && matchesToStringSignature)
|
||||
|
||||
// NB: we intentionally do not check return types
|
||||
private val FirSimpleFunction.matchesEqualsSignature: Boolean
|
||||
get() = valueParameters.size == 1 && valueParameters[0].returnTypeRef.coneType.isNullableAny
|
||||
|
||||
private val FirSimpleFunction.matchesHashCodeSignature: Boolean
|
||||
get() = valueParameters.isEmpty()
|
||||
|
||||
private val FirSimpleFunction.matchesToStringSignature: Boolean
|
||||
get() = valueParameters.isEmpty()
|
||||
}
|
||||
+29
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.FQ_N
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.NULLABLE_STRING
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.PROPERTY_NAME
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_CLASS_OR_OBJECT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_TYPE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOLS
|
||||
@@ -21,9 +22,11 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.TO_S
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.VISIBILITY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.WHEN_MISSING_CASES
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSENCE_OF_PRIMARY_CONSTRUCTOR_FOR_INLINE_CLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_DELEGATED_PROPERTY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_FUNCTION_IN_NON_ABSTRACT_CLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_FUNCTION_WITH_BODY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_MEMBER_NOT_IMPLEMENTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_PROPERTY_IN_NON_ABSTRACT_CLASS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_PROPERTY_WITH_GETTER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_PROPERTY_WITH_INITIALIZER
|
||||
@@ -117,6 +120,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LOCAL_ANNOTATION_
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LOCAL_INTERFACE_NOT_ALLOWED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.LOCAL_OBJECT_NOT_ALLOWED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_COMPANION_OBJECTS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_IMPL_MEMBER_NOT_IMPLEMENTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MULTIPLE_VARARG_PARAMETERS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED
|
||||
@@ -455,6 +459,31 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
TO_STRING
|
||||
)
|
||||
|
||||
map.put(
|
||||
ABSTRACT_MEMBER_NOT_IMPLEMENTED,
|
||||
"{0} is not abstract and does not implement abstract member {1}",
|
||||
RENDER_CLASS_OR_OBJECT,
|
||||
FQ_NAMES_IN_TYPES
|
||||
)
|
||||
map.put(
|
||||
ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED,
|
||||
"{0} is not abstract and does not implement abstract base class member {1}",
|
||||
RENDER_CLASS_OR_OBJECT,
|
||||
FQ_NAMES_IN_TYPES
|
||||
)
|
||||
map.put(
|
||||
MANY_IMPL_MEMBER_NOT_IMPLEMENTED,
|
||||
"{0} must override {1} because it inherits many implementations of it",
|
||||
RENDER_CLASS_OR_OBJECT,
|
||||
FQ_NAMES_IN_TYPES
|
||||
)
|
||||
map.put(
|
||||
MANY_IMPL_MEMBER_NOT_IMPLEMENTED,
|
||||
"{0} must override {1} because it inherits multiple interface methods of it",
|
||||
RENDER_CLASS_OR_OBJECT,
|
||||
FQ_NAMES_IN_TYPES
|
||||
)
|
||||
|
||||
map.put(
|
||||
RETURN_TYPE_MISMATCH_ON_OVERRIDE,
|
||||
"Return type of ''{0}'' is not a subtype of the return type of the overridden member ''{1}''",
|
||||
|
||||
+6
@@ -77,6 +77,12 @@ object FirDiagnosticRenderers {
|
||||
name.asString()
|
||||
}
|
||||
|
||||
val RENDER_CLASS_OR_OBJECT = Renderer { firClass: FirClass<*> ->
|
||||
val name = firClass.classId.relativeClassName.asString()
|
||||
val classOrObject = if (firClass is FirRegularClass) "Class" else "Object"
|
||||
"$classOrObject $name"
|
||||
}
|
||||
|
||||
val RENDER_TYPE = Renderer { t: ConeKotlinType ->
|
||||
// TODO: need a way to tune granuality, e.g., without parameter names in functional types.
|
||||
t.render()
|
||||
|
||||
+1
@@ -40,6 +40,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
|
||||
override val classCheckers: Set<FirClassChecker> = setOf(
|
||||
FirOverrideChecker,
|
||||
FirNotImplementedOverrideChecker,
|
||||
FirThrowableSubclassChecker,
|
||||
FirOpenMemberChecker,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user