diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 7ec23ed675d..bd5bd4159c8 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -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>()?.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() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirNotImplementedOverrideChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirNotImplementedOverrideChecker.kt index 558ff6f7f8a..e0a7b880008 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirNotImplementedOverrideChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirNotImplementedOverrideChecker.kt @@ -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>() val notImplementedIntersectionSymbols = mutableListOf>() val invisibleSymbols = mutableListOf>() - 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() } diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt index dff25a7d0d6..3d30f2ce0e6 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt @@ -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 diff --git a/core/compiler.common/src/org/jetbrains/kotlin/util/ImplementationStatus.kt b/core/compiler.common/src/org/jetbrains/kotlin/util/ImplementationStatus.kt new file mode 100644 index 00000000000..39b9e3d4569 --- /dev/null +++ b/core/compiler.common/src/org/jetbrains/kotlin/util/ImplementationStatus.kt @@ -0,0 +1,34 @@ +/* + * 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.util + +/** Implementation status of a member symbol that is available inside a class scope. */ +enum class ImplementationStatus { + /** This symbol is not implemented and should be implemented if the class is not abstract. */ + NOT_IMPLEMENTED, + + /** The symbol is inheriting multiple non-abstract symbols and hence must be explicitly implemented. */ + AMBIGUOUSLY_INHERITED, + + /** + * This symbol has an inherited implementation, and it can be overridden if desired. For example, it's an open non-abstract member or + * it's automatically synthesized by the Kotlin compiler. + */ + INHERITED_OR_SYNTHESIZED, + + /** The symbol is already implemented in this class. */ + ALREADY_IMPLEMENTED, + + /** + * The symbol is not implemented in the class and it cannot be implemented. For example, it's final in super classes or the current + * class is `expect`. + */ + CANNOT_BE_IMPLEMENTED; + + val shouldBeImplemented: Boolean get() = this == NOT_IMPLEMENTED || this == AMBIGUOUSLY_INHERITED + val isOverridable: Boolean get() = this != ALREADY_IMPLEMENTED && this != CANNOT_BE_IMPLEMENTED +} + diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMembersHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMembersHandler.kt index a1b1ee43307..d0eb46e8665 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMembersHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/ImplementMembersHandler.kt @@ -22,8 +22,8 @@ import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor -import org.jetbrains.kotlin.idea.KotlinBundle import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle import org.jetbrains.kotlin.idea.util.expectedDescriptors import org.jetbrains.kotlin.js.descriptorUtils.hasPrimaryConstructor import org.jetbrains.kotlin.psi.KtClass @@ -37,18 +37,18 @@ open class ImplementMembersHandler : GenerateMembersHandler(), IntentionAction { .map { OverrideMemberChooserObject.create(project, it, it, BodyType.FROM_TEMPLATE) } } - override fun getChooserTitle() = KotlinBundle.message("implement.members.handler.title") + override fun getChooserTitle() = KotlinIdeaCoreBundle.message("implement.members.handler.title") - override fun getNoMembersFoundHint() = KotlinBundle.message("implement.members.handler.no.members.hint") + override fun getNoMembersFoundHint() = KotlinIdeaCoreBundle.message("implement.members.handler.no.members.hint") override fun getText() = familyName - override fun getFamilyName() = KotlinBundle.message("implement.members.handler.family") + override fun getFamilyName() = KotlinIdeaCoreBundle.message("implement.members.handler.family") override fun isAvailable(project: Project, editor: Editor, file: PsiFile) = isValidFor(editor, file) } class ImplementAsConstructorParameter : ImplementMembersHandler() { - override fun getText() = KotlinBundle.message("action.text.implement.as.constructor.parameters") + override fun getText() = KotlinIdeaCoreBundle.message("action.text.implement.as.constructor.parameters") override fun isValidForClass(classOrObject: KtClassOrObject): Boolean { if (classOrObject !is KtClass || classOrObject is KtEnumEntry || classOrObject.isInterface()) return false diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt index 3df7465bb01..cec91ea5f3a 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideMemberChooserObject.kt @@ -321,9 +321,6 @@ private fun generateFunction( } } -private fun BodyType.effectiveBodyType(canBeEmpty: Boolean): BodyType = - if (!canBeEmpty && this == EMPTY_OR_TEMPLATE) FROM_TEMPLATE else this - fun generateUnsupportedOrSuperCall( project: Project, descriptor: CallableMemberDescriptor, diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt new file mode 100644 index 00000000000..16dd6b77edb --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtClassMember.kt @@ -0,0 +1,270 @@ +/* + * 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.idea.core.overrideImplement + +import com.intellij.codeInsight.generation.ClassMember +import com.intellij.codeInsight.generation.MemberChooserObject +import com.intellij.codeInsight.generation.MemberChooserObjectBase +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocCommentOwner +import org.jetbrains.kotlin.idea.core.TemplateKind +import org.jetbrains.kotlin.idea.core.getFunctionBodyTextFromTemplate +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.components.KtDeclarationRendererOptions +import org.jetbrains.kotlin.idea.frontend.api.components.RendererModifier +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtConstructorSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtPossibleExtensionSymbol +import org.jetbrains.kotlin.idea.j2k.IdeaDocCommentConverter +import org.jetbrains.kotlin.idea.kdoc.KDocElementFactory +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.findDocComment.findDocComment +import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier +import org.jetbrains.kotlin.renderer.render +import javax.swing.Icon + +internal data class KtClassMemberInfo( + // TODO: use a `KtSymbolPointer` instead to avoid storing `KtSymbol` in an object after KT-46249 is fixed. + val symbol: KtCallableSymbol, + val memberText: String, + val memberIcon: Icon?, + val containingSymbolText: String?, + val containingSymbolIcon: Icon?, +) { + val isProperty: Boolean get() = symbol is KtPropertySymbol +} + +internal class KtClassMember( + private val memberInfo: KtClassMemberInfo, + val bodyType: BodyType, + val preferConstructorParameter: Boolean +) : MemberChooserObjectBase( + memberInfo.memberText, + memberInfo.memberIcon, +), ClassMember { + val symbol = memberInfo.symbol + override fun getParentNodeDelegate(): MemberChooserObject? = + memberInfo.containingSymbolText?.let { + KtClassOrObjectSymbolChooserObject( + memberInfo.containingSymbolText, + memberInfo.containingSymbolIcon + ) + } +} + +private data class KtClassOrObjectSymbolChooserObject( + val symbolText: String?, + val symbolIcon: Icon? +) : + MemberChooserObjectBase(symbolText, symbolIcon) + +internal fun createKtClassMember( + memberInfo: KtClassMemberInfo, + bodyType: BodyType, + preferConstructorParameter: Boolean +): KtClassMember { + return KtClassMember(memberInfo, bodyType, preferConstructorParameter) +} + +internal fun KtAnalysisSession.generateMember( + project: Project, + ktClassMember: KtClassMember, + targetClass: KtClassOrObject?, + copyDoc: Boolean, + mode: MemberGenerateMode = MemberGenerateMode.OVERRIDE +): KtCallableDeclaration = with(ktClassMember) { + val bodyType = when { + targetClass?.hasExpectModifier() == true -> BodyType.NO_BODY + (symbol as? KtPossibleExtensionSymbol)?.isExtension == true && mode == MemberGenerateMode.OVERRIDE -> BodyType.FROM_TEMPLATE + else -> bodyType + } + + val renderOptions = when (mode) { + MemberGenerateMode.OVERRIDE -> RenderOptions.overrideRenderOptions + MemberGenerateMode.ACTUAL -> RenderOptions.actualRenderOptions + MemberGenerateMode.EXPECT -> RenderOptions.expectRenderOptions + } + if (preferConstructorParameter && symbol is KtPropertySymbol) { + return generateConstructorParameter(project, symbol, renderOptions, mode == MemberGenerateMode.OVERRIDE) + } + + + val newMember: KtCallableDeclaration = when (symbol) { + is KtFunctionSymbol -> generateFunction(project, symbol, renderOptions, bodyType, mode == MemberGenerateMode.OVERRIDE) + is KtPropertySymbol -> generateProperty(project, symbol, renderOptions, bodyType, mode == MemberGenerateMode.OVERRIDE) + else -> error("Unknown member to override: $symbol") + } + + when (mode) { + MemberGenerateMode.ACTUAL -> newMember.addModifier(KtTokens.ACTUAL_KEYWORD) + MemberGenerateMode.EXPECT -> if (targetClass == null) { + newMember.addModifier(KtTokens.EXPECT_KEYWORD) + } + MemberGenerateMode.OVERRIDE -> { + // TODO: add `actual` keyword to the generated member if the target class has `actual` and the generated member corresponds to + // an `expect` member. + } + } + + if (copyDoc) { + val kDoc = when (val originalOverriddenPsi = symbol.originalOverriddenSymbol?.psi) { + is KtDeclaration -> + findDocComment(originalOverriddenPsi) + is PsiDocCommentOwner -> { + val kDocText = originalOverriddenPsi.docComment?.let { IdeaDocCommentConverter.convertDocComment(it) } + if (kDocText.isNullOrEmpty()) null else KDocElementFactory(project).createKDocFromText(kDocText) + } + else -> null + } + if (kDoc != null) { + newMember.addAfter(kDoc, null) + } + } + + return newMember +} + +private fun KtAnalysisSession.generateConstructorParameter( + project: Project, + symbol: KtCallableSymbol, + renderOptions: KtDeclarationRendererOptions, + isOverride: Boolean, +): KtCallableDeclaration { + return KtPsiFactory(project).createParameter(symbol.render(renderOptions.copy(forceRenderingOverrideModifier = isOverride))) +} + +private fun KtAnalysisSession.generateFunction( + project: Project, + symbol: KtFunctionSymbol, + renderOptions: KtDeclarationRendererOptions, + bodyType: BodyType, + isOverride: Boolean, +): KtCallableDeclaration { + val returnType = symbol.annotatedType.type + val returnsUnit = returnType.isUnit + + val body = if (bodyType != BodyType.NO_BODY) { + val delegation = generateUnsupportedOrSuperCall(project, symbol, bodyType, returnsUnit) + val returnPrefix = if (!returnsUnit && bodyType.requiresReturn) "return " else "" + "{$returnPrefix$delegation\n}" + } else "" + + val factory = KtPsiFactory(project) + val functionText = symbol.render(renderOptions.copy(forceRenderingOverrideModifier = isOverride)) + body + return when (symbol) { + is KtConstructorSymbol -> { + if (symbol.isPrimary) { + factory.createPrimaryConstructor(functionText) + } else { + factory.createSecondaryConstructor(functionText) + } + } + else -> factory.createFunction(functionText) + } +} + +private fun KtAnalysisSession.generateProperty( + project: Project, + symbol: KtPropertySymbol, + renderOptions: KtDeclarationRendererOptions, + bodyType: BodyType, + isOverride: Boolean, +): KtCallableDeclaration { + val returnType = symbol.annotatedType.type + val returnsNotUnit = !returnType.isUnit + + val body = + if (bodyType != BodyType.NO_BODY) { + buildString { + append("\nget()") + append(" = ") + append(generateUnsupportedOrSuperCall(project, symbol, bodyType, !returnsNotUnit)) + if (!symbol.isVal) { + append("\nset(value) {}") + } + } + } else "" + return KtPsiFactory(project).createProperty(symbol.render(renderOptions.copy(forceRenderingOverrideModifier = isOverride)) + body) +} + +private fun KtAnalysisSession.generateUnsupportedOrSuperCall( + project: Project, + symbol: T, + bodyType: BodyType, + canBeEmpty: Boolean = true +): String where T : KtNamedSymbol, T : KtCallableSymbol { + when (bodyType.effectiveBodyType(canBeEmpty)) { + BodyType.EMPTY_OR_TEMPLATE -> return "" + BodyType.FROM_TEMPLATE -> { + val templateKind = when (symbol) { + is KtFunctionSymbol -> TemplateKind.FUNCTION + is KtPropertySymbol -> TemplateKind.PROPERTY_INITIALIZER + else -> throw IllegalArgumentException("$symbol must be either a function or a property") + } + return getFunctionBodyTextFromTemplate( + project, + templateKind, + symbol.name.asString(), + symbol.annotatedType.type.render(), + null + ) + } + else -> return buildString { + if (bodyType is BodyType.Delegate) { + append(bodyType.receiverName) + } else { + append("super") + if (bodyType == BodyType.QUALIFIED_SUPER) { + val superClassFqName = symbol.originalContainingClassForOverride?.name?.render() + superClassFqName?.let { + append("<").append(superClassFqName).append(">") + } + } + } + append(".").append(symbol.name.render()) + + if (symbol is KtFunctionSymbol) { + val paramTexts = symbol.valueParameters.map { + val renderedName = it.name.render() + if (it.isVararg) "*$renderedName" else renderedName + } + paramTexts.joinTo(this, prefix = "(", postfix = ")") + } + } + } +} + +private object RenderOptions { + // TODO: Currently rendering has the following problems: +// - flexible types are not rendered correctly, specifically there are problems with the following +// - flexible null type is rendered with `!`, which is not valid Kotlin code +// - Array<(out) ...> (example idea/testData/codeInsight/overrideImplement/javaParameters/foo/Impl.kt.fir.after) +// - incorrect type parameter (example idea/testData/codeInsight/overrideImplement/jdk8/overrideCollectionStream.kt.fir.after) +// - some type annotations should be filtered, for example +// - javax.annotation.Nonnull +// - androidx.annotation.RecentlyNonNull +// - org.jetbrains.annotations.NotNull + val overrideRenderOptions = KtDeclarationRendererOptions( + modifiers = setOf(RendererModifier.OVERRIDE, RendererModifier.ANNOTATIONS), + approximateTypes = true, + renderDefaultParameterValue = false, + ) + + val actualRenderOptions = overrideRenderOptions.copy( + modifiers = setOf(RendererModifier.VISIBILITY, RendererModifier.MODALITY, RendererModifier.OVERRIDE, RendererModifier.INNER) + + ) + val expectRenderOptions = actualRenderOptions.copy( + modifiers = actualRenderOptions.modifiers + RendererModifier.ACTUAL + ) +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtGenerateMembersHandler.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtGenerateMembersHandler.kt new file mode 100644 index 00000000000..59e6e072b21 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtGenerateMembersHandler.kt @@ -0,0 +1,324 @@ +/* + * 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.idea.core.overrideImplement + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.psi.codeStyle.CodeStyleManager +import org.jetbrains.kotlin.idea.core.insertMembersAfter +import org.jetbrains.kotlin.idea.core.moveCaretIntoGeneratedElement +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.analyse +import org.jetbrains.kotlin.idea.frontend.api.components.KtDeclarationRendererOptions +import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions +import org.jetbrains.kotlin.idea.frontend.api.components.RendererModifier +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.tokens.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtClassBody +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.prevSiblingOfSameType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult + +internal abstract class KtGenerateMembersHandler : AbstractGenerateMembersHandler() { + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + override fun generateMembers( + editor: Editor, + classOrObject: KtClassOrObject, + selectedElements: Collection, + copyDoc: Boolean + ) { + // Using hackyAllowRunningOnEdt here because we don't want to pre-populate all possible textual overrides before user selection. + val (commands, insertedBlocks) = hackyAllowRunningOnEdt { + val insertedBlocks = analyse(classOrObject) { + this.generateMembers(editor, classOrObject, selectedElements, copyDoc) + } + // Reference shortening is done in a separate analysis session because the session need to be aware of the newly generated + // members. + val commands = analyse(classOrObject) { + insertedBlocks.mapNotNull { block -> + val declarations = block.declarations.mapNotNull { it.element } + val first = declarations.firstOrNull() ?: return@mapNotNull null + val last = declarations.last() + collectPossibleReferenceShortenings(first.containingKtFile, TextRange(first.startOffset, last.endOffset)) + } + } + commands to insertedBlocks + } + runWriteAction { + commands.forEach { it.invokeShortening() } + val project = classOrObject.project + val codeStyleManager = CodeStyleManager.getInstance(project) + insertedBlocks.forEach { block -> + block.declarations.forEach { declaration -> + declaration.element?.let { element -> + codeStyleManager.reformat( + element + ) + } + } + } + insertedBlocks.firstOrNull()?.declarations?.firstNotNullResult { it.element }?.let { + moveCaretIntoGeneratedElement(editor, it) + } + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.generateMembers( + editor: Editor, + currentClass: KtClassOrObject, + selectedElements: Collection, + copyDoc: Boolean + ): List { + if (selectedElements.isEmpty()) return emptyList() + val selectedMemberSymbolsAndGeneratedPsi: Map = selectedElements.associate { + it.symbol to generateMember(currentClass.project, it, currentClass, copyDoc) + } + + val classBody = currentClass.body + val offset = editor.caretModel.offset + + // Insert members at the cursor position if the cursor is within the class body. Or, if there is no body, generate the body and put + // stuff in it. + if (classBody == null || isCursorInsideClassBodyExcludingBraces(classBody, offset)) { + return runWriteAction { + listOf(InsertedBlock(insertMembersAfter(editor, currentClass, selectedMemberSymbolsAndGeneratedPsi.values))) + } + } + + // Insert members at positions such that the result aligns with ordering of members in super types. + val orderedMembers = getMembersOrderedByRelativePositionsInSuperTypes(currentClass, selectedMemberSymbolsAndGeneratedPsi) + return insertMembersAccordingToPreferredOrder(orderedMembers, classBody.lBrace, editor, currentClass) + } + + private fun isCursorInsideClassBodyExcludingBraces(classBody: KtClassBody, offset: Int): Boolean { + return classBody.textRange.contains(offset) + && classBody.lBrace?.textRange?.contains(offset) == false + && classBody.rBrace?.textRange?.contains(offset) == false + } + + /** + * Given a class and some stub implementation of overridden members, output all the callable members in the desired order. For example, + * consider the following code + * + * ``` + * interface Super { + * fun a() + * fun b() + * fun c() + * } + * + * class Sub: Super { + * override fun b() {} + * } + * ``` + * + * Now this method is invoked with `Sub` as [currentClass] and `Super.a` and `Super.c` as [newMemberSymbolsAndGeneratedPsi]. This + * method outputs `[NewEntry(Sub.a), ExistingEntry(Sub.b), NewEntry(Sub.c)]`. + * + * How does this work? + * + * Initially we put all existing members in [currentClass] into a doubly linked list in the order they appear in the source code. Then, + * for each new member, the algorithm finds a target node nearby which this new member should be inserted. If the algorithm fails to + * find a desired target node, then the new member is inserted at the end. + * + * With the above code as an example, initially the doubly linked list contains `[ExistingEntry(Sub.b)]`. Then for `a`, the algorithm + * somehow (explained below) finds `ExistingEntry(Sub.b)` as the target node before which the new member `a` should be inserted. So now + * the doubly linked list contains `[NewEntry(Sub.a), ExistingEntry(Sub.b)]`. Similar steps are done for `c`. + * + * How does the algorithm find the target node and how does it decide whether to insert the new member before or after the target node? + * + * Throughout the algorithm, we maintain a map that tracks super member declarations for each member in the doubly linked list. For + * example, initially, the map contains `{ Super.b -> ExistingEntry(Sub.b) }` + * + * Given a new member, the algorithm first finds the PSI that declares this member in the super class. Then it traverses all the + * sibling members before this PSI element. With the above example, there is nothing before `Super.a`. Next it traverses all the + * sibling members after this PSI element. With the above example, it finds `Super.b`, which exists in the map. So the algorithm now + * knows `Sub.a` should be inserted before `Sub.b`. + * + * @param currentClass the class where the generated member code will be placed in + * @param newMemberSymbolsAndGeneratedPsi the generated members to insert into the class. For each entry in the map, the key is a + * callable symbol for an overridable member that the user has picked to override (or implement), and the value is the stub + * implementation for the chosen symbol. + */ + private fun KtAnalysisSession.getMembersOrderedByRelativePositionsInSuperTypes( + currentClass: KtClassOrObject, + newMemberSymbolsAndGeneratedPsi: Map + ): List { + + // This doubly linked list tracks the preferred ordering of members. + val sentinelHeadNode = DoublyLinkedNode() + val sentinelTailNode = DoublyLinkedNode() + sentinelHeadNode.append(sentinelTailNode) + + // Traverse existing members in the current class and populate + // - a doubly linked list tracking the order + // - a map that tracks a member (as a doubly linked list node) in the current class and its overridden members in super classes (as + // a PSI element). This map is to allow fast look up from a super PSI element to a member entry in the current class + val existingDeclarations = currentClass.declarations.filterIsInstance() + val superPsiToMemberEntry = mutableMapOf>().apply { + for (existingDeclaration in existingDeclarations) { + val node: DoublyLinkedNode = DoublyLinkedNode(MemberEntry.ExistingEntry(existingDeclaration)) + sentinelTailNode.prepend(node) + val callableSymbol = existingDeclaration.getSymbol() as? KtCallableSymbol ?: continue + for (overriddenSymbol in callableSymbol.getAllOverriddenSymbols()) { + put(overriddenSymbol.psi ?: continue, node) + } + } + } + + // Note on implementation: here we need the original ordering defined in the source code, so we stick to PSI rather than using + // `KtMemberScope` because the latter does not guarantee members are traversed in the original order. For example the + // FIR implementation groups overloaded functions together. + outer@ for ((selectedSymbol, generatedPsi) in newMemberSymbolsAndGeneratedPsi) { + val superSymbol = selectedSymbol.originalOverriddenSymbol + val superPsi = superSymbol?.psi + if (superPsi == null) { + // This normally should not happen, but we just try to play safe here. + sentinelTailNode.prepend(DoublyLinkedNode(MemberEntry.NewEntry(generatedPsi))) + continue + } + var currentPsi = superSymbol.psi?.prevSibling + while (currentPsi != null) { + val matchedNode = superPsiToMemberEntry[currentPsi] + if (matchedNode != null) { + val newNode = DoublyLinkedNode(MemberEntry.NewEntry(generatedPsi)) + matchedNode.append(newNode) + superPsiToMemberEntry[superPsi] = newNode + continue@outer + } + currentPsi = currentPsi.prevSibling + } + currentPsi = superSymbol.psi?.nextSibling + while (currentPsi != null) { + val matchedNode = superPsiToMemberEntry[currentPsi] + if (matchedNode != null) { + val newNode = DoublyLinkedNode(MemberEntry.NewEntry(generatedPsi)) + matchedNode.prepend(newNode) + superPsiToMemberEntry[superPsi] = newNode + continue@outer + } + currentPsi = currentPsi.nextSibling + } + val newNode = DoublyLinkedNode(MemberEntry.NewEntry(generatedPsi)) + superPsiToMemberEntry[superPsi] = newNode + sentinelTailNode.prepend(newNode) + } + return sentinelHeadNode.toListSkippingNulls() + } + + @OptIn(ExperimentalStdlibApi::class) + private fun insertMembersAccordingToPreferredOrder( + symbolsInPreferredOrder: List, + classLeftBrace: PsiElement?, + editor: Editor, + currentClass: KtClassOrObject + ): List { + require(symbolsInPreferredOrder.isNotEmpty()) { "symbolsInPreferredOrder must not be empty" } + var firstAnchor: PsiElement? = null + if (symbolsInPreferredOrder.first() is MemberEntry.NewEntry) { + val firstExistingEntry = symbolsInPreferredOrder.firstIsInstanceOrNull() + if (firstExistingEntry != null) { + firstAnchor = firstExistingEntry.psi.prevSiblingOfSameType() ?: classLeftBrace + } + } + + val insertionBlocks = mutableListOf() + var currentAnchor = firstAnchor + val currentBatch = mutableListOf() + fun updateBatch() { + if (currentBatch.isNotEmpty()) { + insertionBlocks += InsertionBlock(currentBatch.toList(), currentAnchor) + currentBatch.clear() + } + } + for (entry in symbolsInPreferredOrder) { + when (entry) { + is MemberEntry.ExistingEntry -> { + updateBatch() + currentAnchor = entry.psi + } + is MemberEntry.NewEntry -> { + currentBatch += entry.psi + } + } + } + updateBatch() + + return runWriteAction { + insertionBlocks.map { (newDeclarations, anchor) -> + InsertedBlock(insertMembersAfter(editor, currentClass, newDeclarations, anchor = anchor)) + } + } + } + + private class DoublyLinkedNode(val t: T? = null) { + private var prev: DoublyLinkedNode? = null + private var next: DoublyLinkedNode? = null + + fun append(node: DoublyLinkedNode) { + val next = this.next + this.next = node + node.prev = this + node.next = next + next?.prev = node + } + + fun prepend(node: DoublyLinkedNode) { + val prev = this.prev + this.prev = node + node.next = this + node.prev = prev + prev?.next = node + } + + @OptIn(ExperimentalStdlibApi::class) + fun toListSkippingNulls(): List { + var current: DoublyLinkedNode? = this + return buildList { + while (current != null) { + current?.let { + if (it.t != null) add(it.t) + current = it.next + } + } + } + } + } + + private sealed class MemberEntry { + data class ExistingEntry(val psi: KtCallableDeclaration) : MemberEntry() + data class NewEntry(val psi: KtCallableDeclaration) : MemberEntry() + } + + companion object { + val renderOption = KtDeclarationRendererOptions( + modifiers = setOf(RendererModifier.OVERRIDE, RendererModifier.ANNOTATIONS), + renderDeclarationHeader = false, + renderUnitReturnType = true, + typeRendererOptions = KtTypeRendererOptions( + shortQualifiedNames = true, + renderFunctionType = true, + renderAnnotations = false + ) + ) + } + + /** A block of code (represented as a list of Kotlin declarations) that should be inserted at a given anchor. */ + private data class InsertionBlock(val declarations: List, val anchor: PsiElement?) + + /** A block of generated code. The code is represented as a list of Kotlin declarations that are defined one after another. */ + private data class InsertedBlock(val declarations: List>) +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtImplementMembersHandler.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtImplementMembersHandler.kt new file mode 100644 index 00000000000..ece783eef11 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtImplementMembersHandler.kt @@ -0,0 +1,153 @@ +/* + * 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.idea.core.overrideImplement + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.core.overrideImplement.KtImplementMembersHandler.Companion.getUnimplementedMembers +import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle +import org.jetbrains.kotlin.idea.fir.api.fixes.diagnosticFixFactory +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.analyse +import org.jetbrains.kotlin.idea.frontend.api.fir.diagnostics.KtFirDiagnostic +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtIconProvider.getIcon +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.idea.frontend.api.tokens.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtEnumEntry +import org.jetbrains.kotlin.util.ImplementationStatus + +internal open class KtImplementMembersHandler : KtGenerateMembersHandler() { + override fun getChooserTitle() = KotlinIdeaCoreBundle.message("implement.members.handler.title") + + override fun getNoMembersFoundHint() = KotlinIdeaCoreBundle.message("implement.members.handler.no.members.hint") + + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection { + return hackyAllowRunningOnEdt { + analyse(classOrObject) { + getUnimplementedMembers(classOrObject).map { createKtClassMember(it, BodyType.FROM_TEMPLATE, false) } + } + } + } + + companion object { + fun KtAnalysisSession.getUnimplementedMembers(classWithUnimplementedMembers: KtClassOrObject): List { + return getUnimplementedMemberSymbols(classWithUnimplementedMembers.getClassOrObjectSymbol()).map { unimplementedMemberSymbol -> + val containingSymbol = unimplementedMemberSymbol.originalContainingClassForOverride + KtClassMemberInfo( + symbol = unimplementedMemberSymbol, + memberText = unimplementedMemberSymbol.render(renderOption), + memberIcon = getIcon(unimplementedMemberSymbol), + containingSymbolText = containingSymbol?.classIdIfNonLocal?.asSingleFqName()?.toString() + ?: containingSymbol?.name?.asString(), + containingSymbolIcon = containingSymbol?.let { symbol -> getIcon(symbol) } + ) + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.getUnimplementedMemberSymbols(classWithUnimplementedMembers: KtClassOrObjectSymbol): List { + return buildList { + classWithUnimplementedMembers.getMemberScope().getCallableSymbols().forEach { symbol -> + if (!symbol.isVisibleInClass(classWithUnimplementedMembers)) return@forEach + when (symbol.getImplementationStatus(classWithUnimplementedMembers)) { + ImplementationStatus.NOT_IMPLEMENTED -> add(symbol) + ImplementationStatus.AMBIGUOUSLY_INHERITED, + ImplementationStatus.INHERITED_OR_SYNTHESIZED -> { + // This case is to show user abstract members that don't need to be implemented because another super class has provide + // an implementation. For example, given the following + // + // interface A { fun foo() } + // interface B { fun foo() {} } + // class Foo : A, B {} + // + // `Foo` does not need to implement `foo` since it inherits the implementation from `B`. But in the dialog, we should + // allow user to choose `foo` to implement. + symbol.getIntersectionOverriddenSymbols() + .filter { (it as? KtSymbolWithModality)?.modality == Modality.ABSTRACT } + .forEach { add(it) } + } + else -> { + } + } + } + } + } + } +} + +internal class KtImplementMembersQuickfix(private val members: Collection) : KtImplementMembersHandler(), + IntentionAction { + override fun getText() = familyName + override fun getFamilyName() = KotlinIdeaCoreBundle.message("implement.members.handler.family") + + override fun isAvailable(project: Project, editor: Editor, file: PsiFile) = isValidFor(editor, file) + + override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection { + return members.map { createKtClassMember(it, BodyType.FROM_TEMPLATE, false) } + } +} + +internal class KtImplementAsConstructorParameterQuickfix(private val members: Collection) : KtImplementMembersHandler(), + IntentionAction { + override fun getText() = KotlinIdeaCoreBundle.message("action.text.implement.as.constructor.parameters") + + override fun getFamilyName() = KotlinIdeaCoreBundle.message("implement.members.handler.family") + + override fun isAvailable(project: Project, editor: Editor, file: PsiFile) = isValidFor(editor, file) + + override fun isValidForClass(classOrObject: KtClassOrObject): Boolean { + if (classOrObject !is KtClass || classOrObject is KtEnumEntry || classOrObject.isInterface()) return false + // TODO: when MPP support is ready, return false if this class is `actual` and any expect classes have primary constructor. + return members.any { it.isProperty } + } + + override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection { + return members.filter { it.isProperty }.map { createKtClassMember(it, BodyType.FROM_TEMPLATE, true) } + } +} + +object MemberNotImplementedQuickfixFactories { + + val abstractMemberNotImplemented = diagnosticFixFactory { diagnostic -> + getUnimplementedMemberFixes(diagnostic.psi) + } + + val abstractClassMemberNotImplemented = diagnosticFixFactory { diagnostic -> + getUnimplementedMemberFixes(diagnostic.psi) + } + + val manyInterfacesMemberNotImplemented = diagnosticFixFactory { diagnostic -> + getUnimplementedMemberFixes(diagnostic.psi) + } + + val manyImplMemberNotImplemented = diagnosticFixFactory { diagnostic -> + getUnimplementedMemberFixes(diagnostic.psi, false) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.getUnimplementedMemberFixes( + classWithUnimplementedMembers: KtClassOrObject, + includeImplementAsConstructorParameterQuickfix: Boolean = true + ): List { + val unimplementedMembers = getUnimplementedMembers(classWithUnimplementedMembers) + + return buildList { + add(KtImplementMembersQuickfix(unimplementedMembers)) + if (includeImplementAsConstructorParameterQuickfix) { + add(KtImplementAsConstructorParameterQuickfix(unimplementedMembers)) + } + } + } +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtOverrideMembersHandler.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtOverrideMembersHandler.kt new file mode 100644 index 00000000000..6da4f5dd867 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/core/overrideImplement/KtOverrideMembersHandler.kt @@ -0,0 +1,107 @@ +/* + * 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.idea.core.overrideImplement + +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.core.util.KotlinIdeaCoreBundle +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.analyse +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassKind +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtIconProvider.getIcon +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtPropertySymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.idea.frontend.api.tokens.HackToForceAllowRunningAnalyzeOnEDT +import org.jetbrains.kotlin.idea.frontend.api.tokens.hackyAllowRunningOnEdt +import org.jetbrains.kotlin.name.StandardClassIds +import org.jetbrains.kotlin.psi.KtClassOrObject + +internal class KtOverrideMembersHandler : KtGenerateMembersHandler() { + @OptIn(HackToForceAllowRunningAnalyzeOnEDT::class) + override fun collectMembersToGenerate(classOrObject: KtClassOrObject): Collection { + return hackyAllowRunningOnEdt { + analyse(classOrObject) { + collectMembers(classOrObject) + } + } + } + + private fun KtAnalysisSession.collectMembers(classOrObject: KtClassOrObject): List { + val classOrObjectSymbol = classOrObject.getClassOrObjectSymbol() + return getOverridableMembers(classOrObjectSymbol).map { (symbol, bodyType, containingSymbol) -> + KtClassMember( + KtClassMemberInfo( + symbol, + symbol.render(renderOption), + getIcon(symbol), + containingSymbol?.classIdIfNonLocal?.asSingleFqName()?.toString() ?: containingSymbol?.name?.asString(), + containingSymbol?.let { getIcon(it) } + ), + bodyType, + preferConstructorParameter = false + ) + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun KtAnalysisSession.getOverridableMembers(classOrObjectSymbol: KtClassOrObjectSymbol): List { + return buildList { + classOrObjectSymbol.getMemberScope().getCallableSymbols().forEach { symbol -> + if (!symbol.isVisibleInClass(classOrObjectSymbol)) return@forEach + val implementationStatus = symbol.getImplementationStatus(classOrObjectSymbol) ?: return@forEach + if (!implementationStatus.isOverridable) return@forEach + + val intersectionSymbols = symbol.getIntersectionOverriddenSymbols() + val symbolsToProcess = if (intersectionSymbols.size <= 1) { + listOf(symbol) + } else { + val nonAbstractMembers = intersectionSymbols.filter { (it as? KtSymbolWithModality)?.modality != Modality.ABSTRACT } + // If there are non-abstract members, we only want to show override for these non-abstract members. Otherwise, show any + // abstract member to override. + nonAbstractMembers.ifEmpty { + listOf(intersectionSymbols.first()) + } + } + + val hasNoSuperTypesExceptAny = classOrObjectSymbol.superTypes.singleOrNull()?.type?.isAny == true + for (symbolToProcess in symbolsToProcess) { + val originalOverriddenSymbol = symbolToProcess.originalOverriddenSymbol + val containingSymbol = originalOverriddenSymbol?.originalContainingClassForOverride + + val bodyType = when { + classOrObjectSymbol.classKind == KtClassKind.INTERFACE && containingSymbol?.classIdIfNonLocal == StandardClassIds.Any -> { + if (hasNoSuperTypesExceptAny) { + // If an interface does not extends any other interfaces, FE1.0 simply skips members of `Any`. So we mimic + // the same behavior. See idea/testData/codeInsight/overrideImplement/noAnyMembersInInterface.kt + continue + } else { + BodyType.NO_BODY + } + } + (originalOverriddenSymbol as? KtSymbolWithModality)?.modality == Modality.ABSTRACT -> + BodyType.FROM_TEMPLATE + symbolsToProcess.size > 1 -> + BodyType.QUALIFIED_SUPER + else -> + BodyType.SUPER + } + // Ideally, we should simply create `KtClassMember` here and remove the intermediate `OverrideMember` data class. But + // that doesn't work because this callback function is holding a read lock and `symbol.render(renderOption)` requires + // the write lock. + // Hence, we store the data in an intermediate `OverrideMember` data class and do the rendering later in the `map` call. + add(OverrideMember(symbolToProcess, bodyType, containingSymbol)) + } + } + } + } + + private data class OverrideMember(val symbol: KtCallableSymbol, val bodyType: BodyType, val containingSymbol: KtClassOrObjectSymbol?) + + override fun getChooserTitle() = KotlinIdeaCoreBundle.message("override.members.handler.title") + + override fun getNoMembersFoundHint() = KotlinIdeaCoreBundle.message("override.members.handler.no.members.hint") +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt index 1a8d560232f..d54d61fc902 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/findUsages/KotlinFindUsagesSupportFirImpl.kt @@ -18,8 +18,6 @@ import org.jetbrains.kotlin.idea.frontend.api.analyseInModalWindow import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtFunctionSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtNamedSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind import org.jetbrains.kotlin.idea.refactoring.CHECK_SUPER_METHODS_YES_NO_DIALOG @@ -70,12 +68,12 @@ class KotlinFindUsagesSupportFirImpl : KotlinFindUsagesSupport { val analyzeResult = analyseInModalWindow(declaration, KotlinBundle.message("find.usages.progress.text.declaration.superMethods")) { (declaration.getSymbol() as? KtCallableSymbol)?.let { callableSymbol -> - ((callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol)?.let { containingClass -> + callableSymbol.originalContainingClassForOverride?.let { containingClass -> val overriddenSymbols = callableSymbol.getAllOverriddenSymbols() val renderToPsi = overriddenSymbols.mapNotNull { it.psi?.let { psi -> - psi to getClassDescription(psi, (it as? KtSymbolWithKind)?.getContainingSymbol()) + psi to getClassDescription(psi, it.originalContainingClassForOverride) } } diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt index bccc0933a66..48af829459c 100644 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/quickfix/MainKtQuickFixRegistrar.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.quickfix +import org.jetbrains.kotlin.idea.core.overrideImplement.MemberNotImplementedQuickfixFactories import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixRegistrar import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesList import org.jetbrains.kotlin.idea.fir.api.fixes.KtQuickFixesListBuilder @@ -78,6 +79,10 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() { registerApplicator(ChangeTypeQuickFix.changeFunctionReturnTypeOnOverride) registerApplicator(ChangeTypeQuickFix.changePropertyReturnTypeOnOverride) registerApplicator(ChangeTypeQuickFix.changeVariableReturnTypeOnOverride) + registerApplicator(MemberNotImplementedQuickfixFactories.abstractMemberNotImplemented) + registerApplicator(MemberNotImplementedQuickfixFactories.abstractClassMemberNotImplemented) + registerApplicator(MemberNotImplementedQuickfixFactories.manyInterfacesMemberNotImplemented) + registerApplicator(MemberNotImplementedQuickfixFactories.manyImplMemberNotImplemented) } private val mutability = KtQuickFixesListBuilder.registerPsiQuickFix { diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index 70020729335..88ab822f5b1 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -41,7 +41,9 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali KtReferenceResolveMixIn, KtReferenceShortenerMixIn, KtSymbolDeclarationRendererMixIn, - KtVisibilityCheckerMixIn { + KtVisibilityCheckerMixIn, + KtMemberSymbolProviderMixin +{ override val analysisSession: KtAnalysisSession get() = this @@ -94,4 +96,7 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali internal val visibilityChecker: KtVisibilityChecker get() = visibilityCheckerImpl protected abstract val visibilityCheckerImpl: KtVisibilityChecker + + internal val overrideInfoProvider: KtOverrideInfoProvider get() = overrideInfoProviderImpl + protected abstract val overrideInfoProviderImpl: KtOverrideInfoProvider } \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtOverrideInfoProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtOverrideInfoProvider.kt new file mode 100644 index 00000000000..a8938c443b7 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtOverrideInfoProvider.kt @@ -0,0 +1,60 @@ +/* + * 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.idea.frontend.api.components + +import org.jetbrains.kotlin.util.ImplementationStatus +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol + +abstract class KtOverrideInfoProvider : KtAnalysisSessionComponent() { + abstract fun isVisible(memberSymbol: KtCallableSymbol, classSymbol: KtClassOrObjectSymbol): Boolean + abstract fun getImplementationStatus(memberSymbol: KtCallableSymbol, parentClassSymbol: KtClassOrObjectSymbol): ImplementationStatus? + abstract fun getOriginalOverriddenSymbol(symbol: KtCallableSymbol): KtCallableSymbol? + abstract fun getOriginalContainingClassForOverride(symbol: KtCallableSymbol): KtClassOrObjectSymbol? +} + +interface KtMemberSymbolProviderMixin : KtAnalysisSessionMixIn { + + /** Checks if the given symbol (possibly a symbol inherited from a super class) is visible in the given class. */ + fun KtCallableSymbol.isVisibleInClass(classSymbol: KtClassOrObjectSymbol): Boolean = + analysisSession.overrideInfoProvider.isVisible(this, classSymbol) + + /** + * Gets the [ImplementationStatus] of the [this] member symbol in the given [parentClassSymbol]. Or null if this symbol is not a + * member. + */ + fun KtCallableSymbol.getImplementationStatus(parentClassSymbol: KtClassOrObjectSymbol): ImplementationStatus? = + analysisSession.overrideInfoProvider.getImplementationStatus(this, parentClassSymbol) + + /** + * Gets the original symbol for the given callable symbol. In a class scope, a symbol may be derived from symbols declared in super + * classes. For example, consider + * + * ``` + * interface A { + * fun foo(t:T) + * } + * + * interface B: A { + * } + * ``` + * + * In the class scope of `B`, there is a callable symbol `foo` that takes a `String`. This symbol is derived from the original symbol + * in `A` that takes the type parameter `T`. Given such a derived symbol, [originalOverriddenSymbol] recovers the original declared + * symbol. + * + * Such situation can also happens for intersection symbols (in case of multiple super types containing symbols with identical signature + * after specialization) and delegation. + */ + val KtCallableSymbol.originalOverriddenSymbol: KtCallableSymbol? + get() = analysisSession.overrideInfoProvider.getOriginalOverriddenSymbol(this) + + /** + * Gets the class symbol where the given callable symbol is declared. See [originalOverriddenSymbol] for more details. + */ + val KtCallableSymbol.originalContainingClassForOverride: KtClassOrObjectSymbol? + get() = analysisSession.overrideInfoProvider.getOriginalContainingClassForOverride(this) +} diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationRendererProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationRendererProvider.kt index e1934debb6d..4b39ebbc077 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationRendererProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtSymbolDeclarationRendererProvider.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.idea.frontend.api.components import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import javax.swing.Icon /** * KtType to string renderer options @@ -24,6 +25,8 @@ data class KtTypeRendererOptions( * @sample Function0 returns () -> Int */ val renderFunctionType: Boolean = true, + + val renderAnnotations: Boolean = true, ) { companion object { val DEFAULT = KtTypeRendererOptions() @@ -63,6 +66,19 @@ data class KtDeclarationRendererOptions( * Approximate Kotlin not-denotable types into denotable for declarations return type */ val approximateTypes: Boolean = false, + + /** + * Declaration header is something like `abstract class`, `fun`, or `private interface` in a declaration. + */ + val renderDeclarationHeader: Boolean = true, + + /** + * Whether to forcefully add `override` modifier when rendering functions or properties. Note that the [modifiers] option still + * controls whether `override` is rendered. That is, if [modifiers] don't contain `override`, then this flag does not have any effect. + */ + val forceRenderingOverrideModifier: Boolean = false, + + val renderDefaultParameterValue: Boolean = true, ) { companion object { val DEFAULT = KtDeclarationRendererOptions() @@ -82,7 +98,8 @@ enum class RendererModifier(val includeByDefault: Boolean) { CONST(true), LATEINIT(true), FUN(true), - VALUE(true) + VALUE(true), + OPERATOR(true) ; companion object { diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt index 7ce722e3380..239cebeb869 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeInfoProvider.kt @@ -38,6 +38,7 @@ interface KtTypeInfoProviderMixIn : KtAnalysisSessionMixIn { val KtType.isChar: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.CHAR) val KtType.isBoolean: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.BOOLEAN) val KtType.isString: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.STRING) + val KtType.isAny: Boolean get() = isClassTypeWithClassId(DefaultTypeClassIds.ANY) val KtType.isUInt: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uInt) val KtType.isULong: Boolean get() = isClassTypeWithClassId(StandardNames.FqNames.uLong) @@ -84,5 +85,6 @@ object DefaultTypeClassIds { val CHAR = ClassId.topLevel(StandardNames.FqNames._char.toSafe()) val BOOLEAN = ClassId.topLevel(StandardNames.FqNames._boolean.toSafe()) val STRING = ClassId.topLevel(StandardNames.FqNames.string.toSafe()) + val ANY = ClassId.topLevel(StandardNames.FqNames.any.toSafe()) val PRIMITIVES = setOf(INT, LONG, SHORT, BYTE, FLOAT, DOUBLE, CHAR, BOOLEAN) } diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtCallableSymbol.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtCallableSymbol.kt index c9eb1fc63fc..2f952293fe9 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtCallableSymbol.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtCallableSymbol.kt @@ -5,11 +5,12 @@ package org.jetbrains.kotlin.idea.frontend.api.symbols +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtTypedSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.pointers.KtSymbolPointer import org.jetbrains.kotlin.name.CallableId -abstract class KtCallableSymbol : KtSymbol, KtTypedSymbol { +abstract class KtCallableSymbol : KtSymbol, KtTypedSymbol, KtSymbolWithKind { abstract val callableIdIfNonLocal: CallableId? abstract override fun createPointer(): KtSymbolPointer } \ No newline at end of file diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtIconProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtIconProvider.kt new file mode 100644 index 00000000000..08d6b1ed7b4 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/symbols/KtIconProvider.kt @@ -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.idea.frontend.api.symbols + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.Iconable +import com.intellij.psi.PsiClass +import com.intellij.ui.RowIcon +import com.intellij.util.PlatformIcons +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtPossibleMemberSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithModality +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithVisibility +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.isExtension +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtDeclaration +import javax.swing.Icon + +object KtIconProvider { + private val LOG = Logger.getInstance(KtIconProvider::class.java) + + // KtAnalysisSession is unused, but we want to keep it here to force all access to this method has a KtAnalysisSession + @Suppress("unused") + fun KtAnalysisSession.getIcon(ktSymbol: KtSymbol): Icon? { + // logic copied from org.jetbrains.kotlin.idea.KotlinDescriptorIconProvider + val declaration = ktSymbol.psi + return if (declaration?.isValid == true) { + val isClass = declaration is PsiClass || declaration is KtClass + val flags = if (isClass) 0 else Iconable.ICON_FLAG_VISIBILITY + if (declaration is KtDeclaration) { + // kotlin declaration + // visibility and abstraction better detect by a descriptor + getIcon(ktSymbol, flags) ?: declaration.getIcon(flags) + } else { + // Use Java icons if it's not Kotlin declaration + declaration.getIcon(flags) + } + } else { + getIcon(ktSymbol, 0) + } + } + + private fun getIcon(symbol: KtSymbol, flags: Int): Icon? { + var result: Icon = getBaseIcon(symbol) ?: return null + + if (flags and Iconable.ICON_FLAG_VISIBILITY > 0) { + val rowIcon = RowIcon(2) + rowIcon.setIcon(result, 0) + rowIcon.setIcon(getVisibilityIcon(symbol), 1) + result = rowIcon + } + return result + } + + private fun getBaseIcon(symbol: KtSymbol): Icon? { + val isAbstract = (symbol as? KtSymbolWithModality)?.modality == Modality.ABSTRACT + return when (symbol) { + is KtPackageSymbol -> PlatformIcons.PACKAGE_ICON + is KtFunctionLikeSymbol -> { + val isExtension = symbol.isExtension + val isMember = (symbol as? KtPossibleMemberSymbol)?.dispatchType != null + when { + isExtension && isAbstract -> KotlinIcons.ABSTRACT_EXTENSION_FUNCTION + isExtension && !isAbstract -> KotlinIcons.EXTENSION_FUNCTION + isMember && isAbstract -> PlatformIcons.ABSTRACT_METHOD_ICON + isMember && !isAbstract -> PlatformIcons.METHOD_ICON + else -> KotlinIcons.FUNCTION + } + } + is KtClassOrObjectSymbol -> { + when (symbol.classKind) { + KtClassKind.CLASS -> when { + isAbstract -> KotlinIcons.ABSTRACT_CLASS + else -> KotlinIcons.CLASS + } + KtClassKind.ENUM_CLASS, KtClassKind.ENUM_ENTRY -> KotlinIcons.ENUM + KtClassKind.ANNOTATION_CLASS -> KotlinIcons.ANNOTATION + KtClassKind.INTERFACE -> KotlinIcons.INTERFACE + KtClassKind.ANONYMOUS_OBJECT, KtClassKind.OBJECT, KtClassKind.COMPANION_OBJECT -> KotlinIcons.OBJECT + } + } + is KtValueParameterSymbol -> KotlinIcons.PARAMETER + is KtLocalVariableSymbol -> when { + symbol.isVal -> KotlinIcons.VAL + else -> KotlinIcons.VAR + } + is KtPropertySymbol -> when { + symbol.isVal -> KotlinIcons.FIELD_VAL + else -> KotlinIcons.FIELD_VAR + } + is KtTypeParameterSymbol -> PlatformIcons.CLASS_ICON + is KtTypeAliasSymbol -> KotlinIcons.TYPE_ALIAS + + else -> { + LOG.warn("No icon for symbol: $symbol") + null + } + } + } + + private fun getVisibilityIcon(symbol: KtSymbol): Icon? { + return when ((symbol as? KtSymbolWithVisibility)?.visibility?.normalize()) { + Visibilities.Public -> PlatformIcons.PUBLIC_ICON + Visibilities.Protected -> PlatformIcons.PROTECTED_ICON + Visibilities.Private, Visibilities.PrivateToThis -> PlatformIcons.PRIVATE_ICON + Visibilities.Internal -> PlatformIcons.PACKAGE_LOCAL_ICON + else -> null + } + } +} diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt index 565b8e24354..fff31753c22 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/KtFirAnalysisSession.kt @@ -16,13 +16,14 @@ import org.jetbrains.kotlin.idea.fir.low.level.api.api.getFirFile import org.jetbrains.kotlin.idea.frontend.api.InvalidWayOfUsingAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.components.KtVisibilityChecker -import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationRendererProvider import org.jetbrains.kotlin.idea.frontend.api.fir.components.* +import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirOverrideInfoProvider import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbolProvider import org.jetbrains.kotlin.idea.frontend.api.fir.utils.EnclosingDeclarationContext import org.jetbrains.kotlin.idea.frontend.api.fir.utils.recordCompletionContext import org.jetbrains.kotlin.idea.frontend.api.fir.utils.threadLocal +import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile @@ -62,6 +63,8 @@ private constructor( override val expressionInfoProviderImpl = KtFirExpressionInfoProvider(this, token) + override val overrideInfoProviderImpl = KtFirOverrideInfoProvider(this, token) + override val visibilityCheckerImpl: KtVisibilityChecker = KtFirVisibilityChecker(this, token) override val typeProviderImpl = KtFirTypeProvider(this, token) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt index 6d04519e509..39dd082e377 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolContainingDeclarationProvider.kt @@ -16,7 +16,6 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol import org.jetbrains.kotlin.idea.frontend.api.symbols.* import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolKind import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind -import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtObjectLiteralExpression import org.jetbrains.kotlin.psi.KtPrimaryConstructor @@ -28,6 +27,14 @@ internal class KtFirSymbolContainingDeclarationProvider( override fun getContainingDeclaration(symbol: KtSymbolWithKind): KtSymbolWithKind? { if (symbol is KtPackageSymbol) return null if (symbol.symbolKind == KtSymbolKind.TOP_LEVEL) return null + if (symbol is KtCallableSymbol) { + val classId = symbol.callableIdIfNonLocal?.classId + if (classId != null) { + with(analysisSession) { + return classId.getCorrespondingToplevelClassOrObjectSymbol() + } + } + } return when (symbol.origin) { KtSymbolOrigin.SOURCE, KtSymbolOrigin.SOURCE_MEMBER_GENERATED -> getContainingDeclarationForKotlinInSourceSymbol(symbol) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt index 521643c71bc..5ec2b116ede 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationOverridesProvider.kt @@ -13,13 +13,14 @@ import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverrideFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirIntersectionOverridePropertySymbol -import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.components.KtSymbolDeclarationOverridesProvider import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirNamedClassOrObjectSymbol import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol -import org.jetbrains.kotlin.idea.frontend.api.symbols.* -import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbolOrigin +import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken internal class KtFirSymbolDeclarationOverridesProvider( override val analysisSession: KtFirAnalysisSession, @@ -90,7 +91,7 @@ internal class KtFirSymbolDeclarationOverridesProvider( ) { require(callableSymbol is KtFirSymbol<*>) val containingDeclaration = with(analysisSession) { - (callableSymbol as? KtSymbolWithKind)?.getContainingSymbol() as? KtClassOrObjectSymbol + (callableSymbol as? KtCallableSymbol)?.originalContainingClassForOverride } ?: return check(containingDeclaration is KtFirNamedClassOrObjectSymbol) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationRendererProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationRendererProvider.kt index b88de0c5b29..9b73c7051fa 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationRendererProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirSymbolDeclarationRendererProvider.kt @@ -14,8 +14,8 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.renderer.ConeTypeIdeRenderer import org.jetbrains.kotlin.idea.frontend.api.fir.renderer.FirIdeRenderer import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirSymbol import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType -import org.jetbrains.kotlin.idea.frontend.api.symbols.KtSymbol -import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.KtSymbolWithKind +import org.jetbrains.kotlin.idea.frontend.api.symbols.* +import org.jetbrains.kotlin.idea.frontend.api.symbols.markers.* import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken import org.jetbrains.kotlin.idea.frontend.api.types.KtType @@ -39,7 +39,7 @@ internal class KtFirSymbolDeclarationRendererProvider( val phaseNeeded = if (options.renderContainingDeclarations) FirResolvePhase.BODY_RESOLVE else FirResolvePhase.IMPLICIT_TYPES_BODY_RESOLVE - return (containingSymbol ?: symbol).firRef.withFir(phaseNeeded) { fir -> + return symbol.firRef.withFir(phaseNeeded) { fir -> val containingFir = containingSymbol?.firRef?.withFirUnsafe { it } FirIdeRenderer.render(fir, containingFir, options, fir.declarationSiteSession) } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/ConeTypeIdeRenderer.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/ConeTypeIdeRenderer.kt index 4608944f934..6eb89db887e 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/ConeTypeIdeRenderer.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/ConeTypeIdeRenderer.kt @@ -36,7 +36,7 @@ internal class ConeTypeIdeRenderer( private var filterExtensionFunctionType: Boolean = false private fun StringBuilder.renderAnnotationList(annotations: List?) { - if (annotations != null) { + if (annotations != null && options.renderAnnotations) { val filteredExtensionIfNeeded = annotations.applyIf(filterExtensionFunctionType) { annotations.filterNot { it.toAnnotationClassId() == StandardClassIds.extensionFunctionType } } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/FirIdeRenderer.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/FirIdeRenderer.kt index 1c3302de947..6d17c9e4d8b 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/FirIdeRenderer.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/renderer/FirIdeRenderer.kt @@ -50,7 +50,12 @@ internal class FirIdeRenderer private constructor( val approximatedIfNeeded = approximate.ifTrue { PublicTypeApproximator.approximateTypeToPublicDenotable(firRef.coneType, session) } ?: firRef.coneType - return renderType(approximatedIfNeeded, firRef.annotations) + val annotations = if (RendererModifier.ANNOTATIONS in options.modifiers) { + firRef.annotations + } else { + null + } + return renderType(approximatedIfNeeded, annotations) } private fun StringBuilder.renderName(declaration: FirDeclaration) { @@ -133,7 +138,7 @@ internal class FirIdeRenderer private constructor( private fun StringBuilder.renderOverride(callableMember: FirCallableMemberDeclaration<*>) { if (RendererModifier.OVERRIDE !in options.modifiers) return - renderModifier(callableMember.isOverride, "override") + renderModifier(callableMember.isOverride || options.forceRenderingOverrideModifier, "override") } private fun StringBuilder.renderModifier(value: Boolean, modifier: String) { @@ -159,7 +164,7 @@ internal class FirIdeRenderer private constructor( renderSuspendModifier(firMember) renderModifier(firMember.isInline, "inline") renderModifier(isInfix, "infix") - renderModifier(isOperator, "operator") + renderModifier(RendererModifier.OPERATOR in options.modifiers && isOperator, "operator") } private fun StringBuilder.renderSuspendModifier(functionDescriptor: FirMemberDeclaration) { @@ -178,15 +183,17 @@ internal class FirIdeRenderer private constructor( override fun visitProperty(property: FirProperty, data: StringBuilder) = with(data) { appendLine() appendTabs() - renderAnnotations(property) - renderVisibility(property.visibility) - renderModifier(RendererModifier.CONST in options.modifiers && property.isConst, "const") - renderMemberModifiers(property) - renderModalityForCallable(property, containingDeclaration) - renderOverride(property) - renderModifier(RendererModifier.LATEINIT in options.modifiers && property.isLateInit, "lateinit") - renderValVarPrefix(property) - renderTypeParameters(property.typeParameters, true) + if (options.renderDeclarationHeader) { + renderAnnotations(property) + renderVisibility(property.visibility) + renderModifier(RendererModifier.CONST in options.modifiers && property.isConst, "const") + renderMemberModifiers(property) + renderModalityForCallable(property, containingDeclaration) + renderOverride(property) + renderModifier(RendererModifier.LATEINIT in options.modifiers && property.isLateInit, "lateinit") + renderValVarPrefix(property) + renderTypeParameters(property.typeParameters, true) + } renderReceiver(property) renderName(property) @@ -221,11 +228,13 @@ internal class FirIdeRenderer private constructor( with(data) { appendLine() appendTabs() - renderAnnotations(propertyAccessor) - renderVisibility(propertyAccessor.visibility) - renderModalityForCallable(propertyAccessor, containingDeclaration) - renderMemberModifiers(propertyAccessor) - renderAdditionalModifiers(propertyAccessor) + if (options.renderDeclarationHeader) { + renderAnnotations(propertyAccessor) + renderVisibility(propertyAccessor.visibility) + renderModalityForCallable(propertyAccessor, containingDeclaration) + renderMemberModifiers(propertyAccessor) + renderAdditionalModifiers(propertyAccessor) + } append(if (propertyAccessor.isGetter) "get" else "set") if (propertyAccessor.isSetter) { append("(value: ") @@ -248,15 +257,17 @@ internal class FirIdeRenderer private constructor( with(data) { appendLine() appendTabs() - renderAnnotations(simpleFunction) - renderVisibility(simpleFunction.visibility) + if (options.renderDeclarationHeader) { + renderAnnotations(simpleFunction) + renderVisibility(simpleFunction.visibility) - renderModalityForCallable(simpleFunction, containingDeclaration) - renderMemberModifiers(simpleFunction) - renderOverride(simpleFunction) - renderAdditionalModifiers(simpleFunction) - append("fun ") - renderTypeParameters(simpleFunction.typeParameters, true) + renderModalityForCallable(simpleFunction, containingDeclaration) + renderMemberModifiers(simpleFunction) + renderOverride(simpleFunction) + renderAdditionalModifiers(simpleFunction) + append("fun ") + renderTypeParameters(simpleFunction.typeParameters, true) + } renderReceiver(simpleFunction) renderName(simpleFunction) renderValueParameters(simpleFunction.valueParameters) @@ -282,7 +293,9 @@ internal class FirIdeRenderer private constructor( appendLine() appendTabs() - renderAnnotations(anonymousObject) + if (options.renderDeclarationHeader) { + renderAnnotations(anonymousObject) + } append(getClassifierKindPrefix(anonymousObject)) renderSuperTypes(anonymousObject) } @@ -304,7 +317,9 @@ internal class FirIdeRenderer private constructor( check(containingClass is FirDeclaration && (containingClass is FirClass<*> || containingClass is FirEnumEntry)) { "Invalid renderer containing declaration for constructor" } - renderAnnotations(constructor) + if (options.renderDeclarationHeader) { + renderAnnotations(constructor) + } append("constructor") renderValueParameters(constructor.valueParameters) } @@ -325,25 +340,27 @@ internal class FirIdeRenderer private constructor( appendLine() appendTabs() - renderAnnotations(regularClass) - if (regularClass.classKind != ClassKind.ENUM_ENTRY) { - renderVisibility(regularClass.visibility) - } - - val haveNotModality = regularClass.classKind == ClassKind.INTERFACE && regularClass.modality == Modality.ABSTRACT || - regularClass.classKind.isSingleton && regularClass.modality == Modality.FINAL - if (!haveNotModality) { - regularClass.modality?.let { - renderModality(it, regularClass.implicitModalityWithoutExtensions(containingDeclaration)) + if (options.renderDeclarationHeader) { + renderAnnotations(regularClass) + if (regularClass.classKind != ClassKind.ENUM_ENTRY) { + renderVisibility(regularClass.visibility) } + + val haveNotModality = regularClass.classKind == ClassKind.INTERFACE && regularClass.modality == Modality.ABSTRACT || + regularClass.classKind.isSingleton && regularClass.modality == Modality.FINAL + if (!haveNotModality) { + regularClass.modality?.let { + renderModality(it, regularClass.implicitModalityWithoutExtensions(containingDeclaration)) + } + } + renderMemberModifiers(regularClass) + renderModifier(RendererModifier.INNER in options.modifiers && regularClass.isInner, "inner") + renderModifier(RendererModifier.DATA in options.modifiers && regularClass.isData, "data") + renderModifier(RendererModifier.INLINE in options.modifiers && regularClass.isInline, "inline") + //TODO renderModifier(data, RendererModifier.VALUE in modifiers && regularClass.isValue, "value") + renderModifier(RendererModifier.FUN in options.modifiers && regularClass.isFun, "fun") + append(getClassifierKindPrefix(regularClass)) } - renderMemberModifiers(regularClass) - renderModifier(RendererModifier.INNER in options.modifiers && regularClass.isInner, "inner") - renderModifier(RendererModifier.DATA in options.modifiers && regularClass.isData, "data") - renderModifier(RendererModifier.INLINE in options.modifiers && regularClass.isInline, "inline") - //TODO renderModifier(data, RendererModifier.VALUE in modifiers && regularClass.isValue, "value") - renderModifier(RendererModifier.FUN in options.modifiers && regularClass.isFun, "fun") - append(getClassifierKindPrefix(regularClass)) if (!regularClass.isCompanion) { tabRightBySpace() @@ -459,10 +476,12 @@ internal class FirIdeRenderer private constructor( } override fun visitTypeAlias(typeAlias: FirTypeAlias, data: StringBuilder) = with(data) { - renderAnnotations(typeAlias) - renderVisibility(typeAlias.visibility) - renderMemberModifiers(typeAlias) - append("typealias").append(" ") + if (options.renderDeclarationHeader) { + renderAnnotations(typeAlias) + renderVisibility(typeAlias.visibility) + renderMemberModifiers(typeAlias) + append("typealias").append(" ") + } renderName(typeAlias) renderTypeParameters(typeAlias.typeParameters, false) append(" = ").append(renderType(typeAlias.expandedTypeRef)) @@ -561,7 +580,9 @@ internal class FirIdeRenderer private constructor( private fun StringBuilder.renderReceiver(firCallableDeclaration: FirCallableDeclaration<*>) { val receiverType = firCallableDeclaration.receiverTypeRef if (receiverType != null) { - renderAnnotations(firCallableDeclaration) + if (options.renderDeclarationHeader) { + renderAnnotations(firCallableDeclaration) + } val needBrackets = typeIdeRenderer.shouldRenderAsPrettyFunctionType(receiverType.coneType) && receiverType.isMarkedNullable == true @@ -601,14 +622,18 @@ internal class FirIdeRenderer private constructor( } private fun StringBuilder.renderValueParameter(valueParameter: FirValueParameter) { - renderAnnotations(valueParameter) + if (options.renderDeclarationHeader) { + renderAnnotations(valueParameter) + } renderModifier(valueParameter.isCrossinline, "crossinline") renderModifier(valueParameter.isNoinline, "noinline") renderVariable(valueParameter) - val withDefaultValue = valueParameter.defaultValue != null //TODO check if default value is inherited - if (withDefaultValue) { - append(" = ...") + if (options.renderDefaultParameterValue) { + val withDefaultValue = valueParameter.defaultValue != null //TODO check if default value is inherited + if (withDefaultValue) { + append(" = ...") + } } } @@ -624,7 +649,12 @@ internal class FirIdeRenderer private constructor( renderModifier(isVarArg, "vararg") renderName(variable) append(": ") - append(renderType(typeToRender)) + val parameterType = typeToRender.coneType + if (isVarArg) { + append(renderType(parameterType.arrayElementType() ?: parameterType, typeToRender.annotations)) + } else { + append(renderType(typeToRender)) + } } private fun StringBuilder.renderSuperTypes(klass: FirClass<*>) { diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirOverrideInfoProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirOverrideInfoProvider.kt new file mode 100644 index 00000000000..adbe6931848 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/symbols/KtFirOverrideInfoProvider.kt @@ -0,0 +1,91 @@ +/* + * 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.idea.frontend.api.fir.symbols + +import org.jetbrains.kotlin.fir.analysis.checkers.getImplementationStatus +import org.jetbrains.kotlin.fir.analysis.checkers.isVisibleInClass +import org.jetbrains.kotlin.fir.containingClass +import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration +import org.jetbrains.kotlin.fir.declarations.FirClass +import org.jetbrains.kotlin.fir.originalForIntersectionOverrideAttr +import org.jetbrains.kotlin.fir.originalForSubstitutionOverride +import org.jetbrains.kotlin.fir.resolve.ScopeSession +import org.jetbrains.kotlin.fir.resolve.SessionHolderImpl +import org.jetbrains.kotlin.fir.scopes.impl.delegatedWrapperData +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.components.KtOverrideInfoProvider +import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.buildSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtCallableSymbol +import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol +import org.jetbrains.kotlin.idea.frontend.api.tokens.ValidityToken +import org.jetbrains.kotlin.util.ImplementationStatus + +class KtFirOverrideInfoProvider( + override val analysisSession: KtAnalysisSession, + override val token: ValidityToken, +) : KtOverrideInfoProvider() { + + private val firAnalysisSession = analysisSession as KtFirAnalysisSession + + override fun isVisible(memberSymbol: KtCallableSymbol, classSymbol: KtClassOrObjectSymbol): Boolean { + require(memberSymbol is KtFirSymbol<*>) + require(classSymbol is KtFirSymbol<*>) + return memberSymbol.firRef.withFir { memberFir -> + if (memberFir !is FirCallableMemberDeclaration<*>) return@withFir false + classSymbol.firRef.withFir inner@{ parentClassFir -> + if (parentClassFir !is FirClass<*>) return@inner false + memberFir.isVisibleInClass(parentClassFir) + } + } + } + + override fun getImplementationStatus(memberSymbol: KtCallableSymbol, parentClassSymbol: KtClassOrObjectSymbol): ImplementationStatus? { + require(memberSymbol is KtFirSymbol<*>) + require(parentClassSymbol is KtFirSymbol<*>) + return memberSymbol.firRef.withFir { memberFir -> + if (memberFir !is FirCallableMemberDeclaration<*>) return@withFir null + parentClassSymbol.firRef.withFir inner@{ parentClassFir -> + if (parentClassFir !is FirClass<*>) return@inner null + memberFir.getImplementationStatus(SessionHolderImpl(firAnalysisSession.rootModuleSession, ScopeSession()), parentClassFir) + } + } + } + + override fun getOriginalContainingClassForOverride(symbol: KtCallableSymbol): KtClassOrObjectSymbol? { + require(symbol is KtFirSymbol<*>) + return symbol.firRef.withFir { firDeclaration -> + if (firDeclaration !is FirCallableMemberDeclaration<*>) return@withFir null + with(analysisSession) { + getOriginalOverriddenSymbol(firDeclaration)?.containingClass()?.classId?.getCorrespondingToplevelClassOrObjectSymbol() + } + } + } + + override fun getOriginalOverriddenSymbol(symbol: KtCallableSymbol): KtCallableSymbol? { + require(symbol is KtFirSymbol<*>) + return symbol.firRef.withFir { firDeclaration -> + if (firDeclaration !is FirCallableMemberDeclaration<*>) return@withFir null + with(analysisSession) { + getOriginalOverriddenSymbol(firDeclaration) + ?.buildSymbol((analysisSession as KtFirAnalysisSession).firSymbolBuilder) as KtCallableSymbol? + } + } + } + + private fun getOriginalOverriddenSymbol(member: FirCallableMemberDeclaration<*>): FirCallableMemberDeclaration<*>? { + val originalForSubstitutionOverride = member.originalForSubstitutionOverride + if (originalForSubstitutionOverride != null) return getOriginalOverriddenSymbol(originalForSubstitutionOverride) + + val originalForIntersectionOverrideAttr = member.originalForIntersectionOverrideAttr + if (originalForIntersectionOverrideAttr != null) return getOriginalOverriddenSymbol(originalForIntersectionOverrideAttr) + + val delegatedWrapperData = member.delegatedWrapperData + if (delegatedWrapperData != null) return getOriginalOverriddenSymbol(delegatedWrapperData.wrapped) + + return member + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt b/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt new file mode 100644 index 00000000000..fddeb119a3a --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt @@ -0,0 +1,4 @@ +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE) annotation class base + +fun foo1(vararg ints: Int) {} +fun foo2(@base vararg ints: @base Int) {} diff --git a/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt.rendered b/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt.rendered new file mode 100644 index 00000000000..47c9203032b --- /dev/null +++ b/idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt.rendered @@ -0,0 +1,3 @@ +@Target(allowedTargets = NOT_CONST_EXPRESSION) annotation class base +fun foo1(vararg ints: Int) +fun foo2(@base vararg ints: @base Int) diff --git a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/RendererTestGenerated.java b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/RendererTestGenerated.java index b68fbd18d5d..94c886c708a 100644 --- a/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/RendererTestGenerated.java +++ b/idea/idea-frontend-fir/tests/org/jetbrains/kotlin/idea/frontend/api/components/RendererTestGenerated.java @@ -154,6 +154,11 @@ public class RendererTestGenerated extends AbstractRendererTest { runTest("idea/idea-frontend-fir/testData/components/declarationRenderer/typeParameters.kt"); } + @TestMetadata("vararg.kt") + public void testVararg() throws Exception { + runTest("idea/idea-frontend-fir/testData/components/declarationRenderer/vararg.kt"); + } + @TestMetadata("where.kt") public void testWhere() throws Exception { runTest("idea/idea-frontend-fir/testData/components/declarationRenderer/where.kt"); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinIcons.java b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/KotlinIcons.java similarity index 88% rename from idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinIcons.java rename to idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/KotlinIcons.java index a1f28ffd072..8014cead2f8 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/KotlinIcons.java +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/KotlinIcons.java @@ -1,17 +1,6 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 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.idea; diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/AbstractGenerateMembersHandler.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/AbstractGenerateMembersHandler.kt index df125b114c8..35b56492661 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/AbstractGenerateMembersHandler.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/AbstractGenerateMembersHandler.kt @@ -28,7 +28,7 @@ abstract class AbstractGenerateMembersHandler : LanguageCodeIns protected abstract fun getNoMembersFoundHint(): String - protected abstract fun generateMembers( + abstract fun generateMembers( editor: Editor, classOrObject: KtClassOrObject, selectedElements: Collection, @@ -74,8 +74,7 @@ abstract class AbstractGenerateMembersHandler : LanguageCodeIns } val copyDoc: Boolean - val selectedElements: Collection + val selectedElements: Collection if (implementAll) { selectedElements = members copyDoc = false diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/BodyType.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/BodyType.kt index a8fc319b429..e2b63215e8c 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/BodyType.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/core/overrideImplement/BodyType.kt @@ -13,4 +13,7 @@ sealed class BodyType(val requiresReturn: Boolean = true) { object QUALIFIED_SUPER : BodyType() class Delegate(val receiverName: String) : BodyType() -} \ No newline at end of file + + fun effectiveBodyType(canBeEmpty: Boolean): BodyType = if (!canBeEmpty && this == EMPTY_OR_TEMPLATE) FROM_TEMPLATE else this +} + diff --git a/idea/resources-fir/META-INF/plugin.xml b/idea/resources-fir/META-INF/plugin.xml index ff90187d1d0..556c4ed65e7 100644 --- a/idea/resources-fir/META-INF/plugin.xml +++ b/idea/resources-fir/META-INF/plugin.xml @@ -144,6 +144,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu + +