FIR IDE: add action to override/implement missing members
This commit adds the following actions: - quickfix to implement missing members - quickfix to implement missing members as constructor parameters - action to implement members (Code - Generate - Implement) - action to override members (Code - Generate - Override) The current implementation is still missing some pieces, which will be addressed in future changes. - fully qualified names are not shorten - some Kotlin types are not rendered correctly
This commit is contained in:
committed by
TeamCityServer
parent
34387e228f
commit
00031c8eb1
+109
-8
@@ -9,14 +9,12 @@ import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.FirSymbolOwner
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.modalityModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.overrideModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.visibilityModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.getChild
|
||||
import org.jetbrains.kotlin.fir.containingClass
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirComponentCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
@@ -24,6 +22,7 @@ import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.SessionHolder
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.inference.isBuiltinFunctionalType
|
||||
import org.jetbrains.kotlin.fir.resolve.symbolProvider
|
||||
@@ -32,11 +31,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike
|
||||
import org.jetbrains.kotlin.fir.scopes.ProcessorAction
|
||||
import org.jetbrains.kotlin.fir.scopes.processOverriddenFunctions
|
||||
import org.jetbrains.kotlin.fir.scopes.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.typeContext
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
@@ -49,6 +44,8 @@ import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.types.model.KotlinTypeMarker
|
||||
import org.jetbrains.kotlin.types.model.TypeCheckerProviderContext
|
||||
import org.jetbrains.kotlin.util.ImplementationStatus
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
private val INLINE_ONLY_ANNOTATION_CLASS_ID = ClassId.topLevel(FqName("kotlin.internal.InlineOnly"))
|
||||
@@ -428,3 +425,107 @@ fun isSubtypeOfForFunctionalTypeReturningUnit(context: ConeInferenceContext, sub
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun FirCallableMemberDeclaration<*>.isVisibleInClass(parentClass: FirClass<*>): Boolean {
|
||||
val classPackage = parentClass.symbol.classId.packageFqName
|
||||
if (visibility == Visibilities.Private ||
|
||||
!visibility.visibleFromPackage(classPackage, symbol.callableId.packageName)
|
||||
) return false
|
||||
if (visibility == Visibilities.Internal &&
|
||||
declarationSiteSession !== parentClass.declarationSiteSession
|
||||
) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the [ImplementationStatus] for this member.
|
||||
*
|
||||
* @param parentClass the contextual class for this query.
|
||||
*/
|
||||
fun FirCallableMemberDeclaration<*>.getImplementationStatus(sessionHolder: SessionHolder, parentClass: FirClass<*>): ImplementationStatus {
|
||||
val containingClass = getContainingClass(sessionHolder)
|
||||
val symbol = this.symbol
|
||||
if (symbol is FirIntersectionCallableSymbol) {
|
||||
if (containingClass === parentClass && symbol.subjectToManyNotImplemented(sessionHolder)) {
|
||||
return ImplementationStatus.AMBIGUOUSLY_INHERITED
|
||||
}
|
||||
// In Java 8, non-abstract intersection overrides having abstract symbol from base class
|
||||
// still should be implemented in current class (even when they have default interface implementation)
|
||||
if (symbol.intersections.any {
|
||||
val fir = (it.fir as FirCallableMemberDeclaration).unwrapFakeOverrides()
|
||||
fir.isAbstract && (fir.getContainingClass(sessionHolder) as? FirRegularClass)?.classKind == ClassKind.CLASS
|
||||
}
|
||||
) {
|
||||
// Exception from the rule above: interface implementation via delegation
|
||||
if (symbol.intersections.none {
|
||||
val fir = (it.fir as FirCallableMemberDeclaration)
|
||||
fir.origin == FirDeclarationOrigin.Delegated && !fir.isAbstract
|
||||
}
|
||||
) {
|
||||
return ImplementationStatus.NOT_IMPLEMENTED
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this is FirSimpleFunction) {
|
||||
if (parentClass is FirRegularClass && parentClass.isData && matchesDataClassSyntheticMemberSignatures) {
|
||||
return ImplementationStatus.INHERITED_OR_SYNTHESIZED
|
||||
}
|
||||
// TODO: suspend function overridden by a Java class in the middle is not properly regarded as an override
|
||||
if (isSuspend) {
|
||||
return ImplementationStatus.INHERITED_OR_SYNTHESIZED
|
||||
}
|
||||
}
|
||||
return when {
|
||||
isFinal -> ImplementationStatus.CANNOT_BE_IMPLEMENTED
|
||||
containingClass === parentClass && origin == FirDeclarationOrigin.Source -> ImplementationStatus.ALREADY_IMPLEMENTED
|
||||
containingClass is FirRegularClass && containingClass.isExpect -> ImplementationStatus.CANNOT_BE_IMPLEMENTED
|
||||
isAbstract -> ImplementationStatus.NOT_IMPLEMENTED
|
||||
else -> ImplementationStatus.INHERITED_OR_SYNTHESIZED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun FirIntersectionCallableSymbol.subjectToManyNotImplemented(sessionHolder: SessionHolder): Boolean {
|
||||
var nonAbstractCountInClass = 0
|
||||
var nonAbstractCountInInterface = 0
|
||||
var abstractCountInInterface = 0
|
||||
for (intersectionSymbol in intersections) {
|
||||
val intersection = intersectionSymbol.fir as FirCallableMemberDeclaration
|
||||
val containingClass = intersection.getContainingClass(sessionHolder) as? FirRegularClass
|
||||
val hasInterfaceContainer = containingClass?.classKind == ClassKind.INTERFACE
|
||||
if (intersection.modality != Modality.ABSTRACT) {
|
||||
if (hasInterfaceContainer) {
|
||||
nonAbstractCountInInterface++
|
||||
} else {
|
||||
nonAbstractCountInClass++
|
||||
}
|
||||
} else if (hasInterfaceContainer) {
|
||||
abstractCountInInterface++
|
||||
}
|
||||
if (nonAbstractCountInClass + nonAbstractCountInInterface > 1) {
|
||||
return true
|
||||
}
|
||||
if (nonAbstractCountInInterface > 0 && abstractCountInInterface > 0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private val FirSimpleFunction.matchesDataClassSyntheticMemberSignatures: Boolean
|
||||
get() = (this.name == OperatorNameConventions.EQUALS && matchesEqualsSignature) ||
|
||||
(this.name == HASHCODE_NAME && matchesHashCodeSignature) ||
|
||||
(this.name == OperatorNameConventions.TO_STRING && matchesToStringSignature)
|
||||
|
||||
private fun FirSymbolOwner<*>.getContainingClass(sessionHolder: SessionHolder): FirClassLikeDeclaration<*>? =
|
||||
this.safeAs<FirCallableMemberDeclaration<*>>()?.containingClass()?.toSymbol(sessionHolder.session)?.fir
|
||||
|
||||
// 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()
|
||||
|
||||
+17
-127
@@ -8,12 +8,9 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.*
|
||||
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.checkers.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ABSTRACT_MEMBER_NOT_IMPLEMENTED
|
||||
@@ -24,15 +21,14 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_IMPL_MEMBER_
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.OVERRIDING_FINAL_MEMBER_BY_DELEGATION
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.containingClass
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.languageVersionSettings
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverrideFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverridePropertySymbol
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.isNullableAny
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.fir.unwrapFakeOverrides
|
||||
import org.jetbrains.kotlin.util.ImplementationStatus
|
||||
|
||||
object FirNotImplementedOverrideChecker : FirClassChecker() {
|
||||
|
||||
@@ -51,115 +47,24 @@ object FirNotImplementedOverrideChecker : FirClassChecker() {
|
||||
val notImplementedSymbols = mutableListOf<FirCallableSymbol<*>>()
|
||||
val notImplementedIntersectionSymbols = mutableListOf<FirCallableSymbol<*>>()
|
||||
val invisibleSymbols = 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 &&
|
||||
declarationSiteSession !== declaration.declarationSiteSession
|
||||
) return true
|
||||
return false
|
||||
}
|
||||
|
||||
fun FirIntersectionCallableSymbol.subjectToManyNotImplemented(): Boolean {
|
||||
var nonAbstractCountInClass = 0
|
||||
var nonAbstractCountInInterface = 0
|
||||
var abstractCountInInterface = 0
|
||||
for (intersectionSymbol in intersections) {
|
||||
val intersection = intersectionSymbol.fir as FirCallableMemberDeclaration
|
||||
val containingClass = intersection.getContainingClass(context) as? FirRegularClass
|
||||
val hasInterfaceContainer = containingClass?.classKind == ClassKind.INTERFACE
|
||||
if (intersection.modality != Modality.ABSTRACT) {
|
||||
if (hasInterfaceContainer) {
|
||||
nonAbstractCountInInterface++
|
||||
} else {
|
||||
nonAbstractCountInClass++
|
||||
}
|
||||
} else if (hasInterfaceContainer) {
|
||||
abstractCountInInterface++
|
||||
fun collectSymbol(symbol: FirCallableSymbol<*>) {
|
||||
val fir = symbol.fir as? FirCallableMemberDeclaration<*> ?: return
|
||||
when (fir.getImplementationStatus(context.sessionHolder, declaration)) {
|
||||
ImplementationStatus.AMBIGUOUSLY_INHERITED -> notImplementedIntersectionSymbols.add(symbol)
|
||||
ImplementationStatus.NOT_IMPLEMENTED -> when {
|
||||
fir.isVisibleInClass(declaration) -> notImplementedSymbols.add(symbol)
|
||||
else -> invisibleSymbols.add(symbol)
|
||||
}
|
||||
if (nonAbstractCountInClass + nonAbstractCountInInterface > 1) {
|
||||
return true
|
||||
}
|
||||
if (nonAbstractCountInInterface > 0 && abstractCountInInterface > 0) {
|
||||
return true
|
||||
else -> {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun FirIntersectionCallableSymbol.shouldBeImplemented(): Boolean {
|
||||
// In Java 8, non-abstract intersection overrides having abstract symbol from base class
|
||||
// still should be implemented in current class (even when they have default interface implementation)
|
||||
if (intersections.none {
|
||||
val fir = (it.fir as FirCallableMemberDeclaration).unwrapFakeOverrides()
|
||||
fir.isAbstract && (fir.getContainingClass(context) as? FirRegularClass)?.classKind == ClassKind.CLASS
|
||||
}
|
||||
) return false
|
||||
// Exception from the rule above: interface implementation via delegation
|
||||
if (intersections.any {
|
||||
val fir = (it.fir as FirCallableMemberDeclaration)
|
||||
fir.origin == FirDeclarationOrigin.Delegated && !fir.isAbstract
|
||||
}
|
||||
) return false
|
||||
return true
|
||||
}
|
||||
|
||||
fun FirCallableMemberDeclaration<*>.shouldBeImplemented(): Boolean {
|
||||
val containingClass = getContainingClass(context)
|
||||
if (containingClass is FirRegularClass && containingClass.isExpect) return false
|
||||
if (!isAbstract) {
|
||||
val symbol = symbol as? FirIntersectionCallableSymbol ?: return false
|
||||
return symbol.shouldBeImplemented()
|
||||
}
|
||||
if (containingClass === declaration && origin == FirDeclarationOrigin.Source) return false
|
||||
return true
|
||||
}
|
||||
|
||||
for (name in classScope.getCallableNames()) {
|
||||
classScope.processFunctionsByName(name) { namedFunctionSymbol ->
|
||||
val simpleFunction = namedFunctionSymbol.fir
|
||||
if (namedFunctionSymbol is FirIntersectionOverrideFunctionSymbol) {
|
||||
if (simpleFunction.getContainingClass(context) === declaration &&
|
||||
namedFunctionSymbol.subjectToManyNotImplemented()
|
||||
) {
|
||||
notImplementedIntersectionSymbols += namedFunctionSymbol
|
||||
return@processFunctionsByName
|
||||
}
|
||||
}
|
||||
if (!simpleFunction.shouldBeImplemented()) return@processFunctionsByName
|
||||
if (declaration is FirRegularClass && 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
|
||||
if (simpleFunction.isInvisible()) {
|
||||
invisibleSymbols += namedFunctionSymbol
|
||||
} else {
|
||||
notImplementedSymbols += namedFunctionSymbol
|
||||
}
|
||||
}
|
||||
classScope.processPropertiesByName(name) { propertySymbol ->
|
||||
val property = propertySymbol.fir as? FirProperty ?: return@processPropertiesByName
|
||||
if (propertySymbol is FirIntersectionOverridePropertySymbol) {
|
||||
if (property.getContainingClass(context) === declaration &&
|
||||
propertySymbol.subjectToManyNotImplemented()
|
||||
) {
|
||||
notImplementedIntersectionSymbols += propertySymbol
|
||||
return@processPropertiesByName
|
||||
}
|
||||
}
|
||||
if (!property.shouldBeImplemented()) return@processPropertiesByName
|
||||
|
||||
if (property.isInvisible()) {
|
||||
invisibleSymbols += propertySymbol
|
||||
} else {
|
||||
notImplementedSymbols += propertySymbol
|
||||
}
|
||||
}
|
||||
classScope.processFunctionsByName(name, ::collectSymbol)
|
||||
classScope.processPropertiesByName(name, ::collectSymbol)
|
||||
}
|
||||
|
||||
if (notImplementedSymbols.isNotEmpty()) {
|
||||
@@ -237,19 +142,4 @@ object FirNotImplementedOverrideChecker : FirClassChecker() {
|
||||
|
||||
private fun FirCallableDeclaration<*>.isFromInterfaceOrEnum(context: CheckerContext): Boolean =
|
||||
(getContainingClass(context) as? FirRegularClass)?.let { it.isInterface || it.isEnumClass } == 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()
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ inline val FirRegularClass.isFun get() = status.isFun
|
||||
inline val FirMemberDeclaration.modality get() = status.modality
|
||||
inline val FirMemberDeclaration.isAbstract get() = status.modality == Modality.ABSTRACT
|
||||
inline val FirMemberDeclaration.isOpen get() = status.modality == Modality.OPEN
|
||||
inline val FirMemberDeclaration.isFinal: Boolean
|
||||
get() {
|
||||
// member with unspecified modality is final
|
||||
val modality = status.modality ?: return true
|
||||
return modality == Modality.FINAL
|
||||
}
|
||||
|
||||
inline val FirMemberDeclaration.visibility: Visibility get() = status.visibility
|
||||
inline val FirMemberDeclaration.effectiveVisibility: EffectiveVisibility
|
||||
|
||||
Reference in New Issue
Block a user