Add actual: handle already existing declarations more precisely

#KT-23693 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-11-15 11:34:55 +03:00
parent 46a5d76254
commit 5c6f776c09
7 changed files with 56 additions and 11 deletions
@@ -28,20 +28,20 @@ import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
class AddActualFix( class AddActualFix(
actualClassOrObject: KtClassOrObject, actualClassOrObject: KtClassOrObject,
expectedClassOrObject: KtClassOrObject expectedClassOrObject: KtClassOrObject,
missedDeclarations: List<KtDeclaration>
) : KotlinQuickFixAction<KtClassOrObject>(actualClassOrObject) { ) : KotlinQuickFixAction<KtClassOrObject>(actualClassOrObject) {
private val expectedClassPointer = expectedClassOrObject.createSmartPointer() private val expectedClassPointer = expectedClassOrObject.createSmartPointer()
private val missedDeclarationPointers = missedDeclarations.map { it.createSmartPointer() }
override fun getFamilyName() = text override fun getFamilyName() = text
override fun getText() = "Add missing actual members" override fun getText() = "Add missing actual members"
@@ -49,12 +49,12 @@ class AddActualFix(
override fun invoke(project: Project, editor: Editor?, file: KtFile) { override fun invoke(project: Project, editor: Editor?, file: KtFile) {
val element = element ?: return val element = element ?: return
val expectedClass = expectedClassPointer.element ?: return val expectedClass = expectedClassPointer.element ?: return
val missedDeclarations = missedDeclarationPointers.mapNotNull { it.element }
if (missedDeclarations.isEmpty()) return
val factory = KtPsiFactory(element) val factory = KtPsiFactory(element)
val pureActualClass = factory.generateClassOrObjectByExpectedClass( val pureActualClass = factory.generateClassOrObjectByExpectedClass(
project, expectedClass, actualNeeded = true, project, expectedClass, actualNeeded = true,
existingDeclarations = element.declarations + missedDeclarations = missedDeclarations
element.primaryConstructor?.valueParameters?.filter { it.hasValOrVar() }.orEmpty() +
listOfNotNull(element.primaryConstructor)
) )
fun PsiElement.clean() { fun PsiElement.clean() {
@@ -74,11 +74,20 @@ class AddActualFix(
companion object : KotlinSingleIntentionActionFactory() { companion object : KotlinSingleIntentionActionFactory() {
override fun createAction(diagnostic: Diagnostic): IntentionAction? { override fun createAction(diagnostic: Diagnostic): IntentionAction? {
val incompatibleMap = DiagnosticFactory.cast(diagnostic, Errors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS).b val incompatibleMap = DiagnosticFactory.cast(diagnostic, Errors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS).b
val expectedClassDescriptor = incompatibleMap.firstOrNull()?.first?.containingDeclaration as? ClassDescriptor val expectedClassDescriptor = incompatibleMap.firstOrNull()?.first?.containingDeclaration as? ClassDescriptor
?: return null ?: return null
val expectedClassOrObject = DescriptorToSourceUtils.descriptorToDeclaration(expectedClassDescriptor) as? KtClassOrObject val expectedClassOrObject = DescriptorToSourceUtils.descriptorToDeclaration(expectedClassDescriptor) as? KtClassOrObject
?: return null ?: return null
return (diagnostic.psiElement as? KtClassOrObject)?.let { AddActualFix(it, expectedClassOrObject) }
val missedDeclarations = incompatibleMap.mapNotNull {
DescriptorToSourceUtils.descriptorToDeclaration(it.first) as? KtDeclaration
}
if (missedDeclarations.isEmpty()) return null
return (diagnostic.psiElement as? KtClassOrObject)?.let {
AddActualFix(it, expectedClassOrObject, missedDeclarations)
}
} }
} }
} }
@@ -223,7 +223,8 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
project: Project, project: Project,
expectedClass: KtClassOrObject, expectedClass: KtClassOrObject,
actualNeeded: Boolean, actualNeeded: Boolean,
existingDeclarations: List<KtDeclaration> = emptyList() // If null, all expect class declarations are missed (so none from them exists)
missedDeclarations: List<KtDeclaration>? = null
): KtClassOrObject { ): KtClassOrObject {
fun areCompatible(first: KtFunction, second: KtFunction) = fun areCompatible(first: KtFunction, second: KtFunction) =
first.valueParameters.size == second.valueParameters.size && first.valueParameters.size == second.valueParameters.size &&
@@ -232,7 +233,7 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
} }
fun KtDeclaration.exists() = fun KtDeclaration.exists() =
existingDeclarations.any { missedDeclarations != null && missedDeclarations.none {
name == it.name && when (this) { name == it.name && when (this) {
is KtConstructor<*> -> it is KtConstructor<*> && areCompatible(this, it) is KtConstructor<*> -> it is KtConstructor<*> && areCompatible(this, it)
is KtNamedFunction -> it is KtNamedFunction && areCompatible(this, it) is KtNamedFunction -> it is KtNamedFunction && areCompatible(this, it)
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect object O {
fun <T : Any> hello(): MutableMap<String, T>
}
@@ -0,0 +1,5 @@
// DISABLE-ERRORS
expect object O {
fun <T : Any> hello(): MutableMap<String, T>
}
@@ -0,0 +1,8 @@
// "Add missing actual members" "true"
// DISABLE-ERRORS
actual object <caret>O {
fun <T> hello(): MutableMap<String, T> {
TODO("not implemented")
}
}
@@ -0,0 +1,12 @@
// "Add missing actual members" "true"
// DISABLE-ERRORS
actual object O {
fun <T> hello(): MutableMap<String, T> {
TODO("not implemented")
}
actual fun <T : Any> hello(): MutableMap<String, T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
@@ -109,6 +109,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
runTest("idea/testData/multiModuleQuickFix/classFunction/"); runTest("idea/testData/multiModuleQuickFix/classFunction/");
} }
@TestMetadata("classFunctionSameSignature")
public void testClassFunctionSameSignature() throws Exception {
runTest("idea/testData/multiModuleQuickFix/classFunctionSameSignature/");
}
@TestMetadata("classFunctionWithConstructor") @TestMetadata("classFunctionWithConstructor")
public void testClassFunctionWithConstructor() throws Exception { public void testClassFunctionWithConstructor() throws Exception {
runTest("idea/testData/multiModuleQuickFix/classFunctionWithConstructor/"); runTest("idea/testData/multiModuleQuickFix/classFunctionWithConstructor/");