Add actual: handle already existing declarations more precisely
#KT-23693 Fixed
This commit is contained in:
@@ -28,20 +28,20 @@ import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinQuickFixAction
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
|
||||
class AddActualFix(
|
||||
actualClassOrObject: KtClassOrObject,
|
||||
expectedClassOrObject: KtClassOrObject
|
||||
expectedClassOrObject: KtClassOrObject,
|
||||
missedDeclarations: List<KtDeclaration>
|
||||
) : KotlinQuickFixAction<KtClassOrObject>(actualClassOrObject) {
|
||||
|
||||
private val expectedClassPointer = expectedClassOrObject.createSmartPointer()
|
||||
|
||||
private val missedDeclarationPointers = missedDeclarations.map { it.createSmartPointer() }
|
||||
|
||||
override fun getFamilyName() = text
|
||||
|
||||
override fun getText() = "Add missing actual members"
|
||||
@@ -49,12 +49,12 @@ class AddActualFix(
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val element = element ?: return
|
||||
val expectedClass = expectedClassPointer.element ?: return
|
||||
val missedDeclarations = missedDeclarationPointers.mapNotNull { it.element }
|
||||
if (missedDeclarations.isEmpty()) return
|
||||
val factory = KtPsiFactory(element)
|
||||
val pureActualClass = factory.generateClassOrObjectByExpectedClass(
|
||||
project, expectedClass, actualNeeded = true,
|
||||
existingDeclarations = element.declarations +
|
||||
element.primaryConstructor?.valueParameters?.filter { it.hasValOrVar() }.orEmpty() +
|
||||
listOfNotNull(element.primaryConstructor)
|
||||
missedDeclarations = missedDeclarations
|
||||
)
|
||||
|
||||
fun PsiElement.clean() {
|
||||
@@ -74,11 +74,20 @@ class AddActualFix(
|
||||
companion object : KotlinSingleIntentionActionFactory() {
|
||||
override fun createAction(diagnostic: Diagnostic): IntentionAction? {
|
||||
val incompatibleMap = DiagnosticFactory.cast(diagnostic, Errors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS).b
|
||||
|
||||
val expectedClassDescriptor = incompatibleMap.firstOrNull()?.first?.containingDeclaration as? ClassDescriptor
|
||||
?: return null
|
||||
val expectedClassOrObject = DescriptorToSourceUtils.descriptorToDeclaration(expectedClassDescriptor) as? KtClassOrObject
|
||||
?: 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,
|
||||
expectedClass: KtClassOrObject,
|
||||
actualNeeded: Boolean,
|
||||
existingDeclarations: List<KtDeclaration> = emptyList()
|
||||
// If null, all expect class declarations are missed (so none from them exists)
|
||||
missedDeclarations: List<KtDeclaration>? = null
|
||||
): KtClassOrObject {
|
||||
fun areCompatible(first: KtFunction, second: KtFunction) =
|
||||
first.valueParameters.size == second.valueParameters.size &&
|
||||
@@ -232,7 +233,7 @@ internal fun KtPsiFactory.generateClassOrObjectByExpectedClass(
|
||||
}
|
||||
|
||||
fun KtDeclaration.exists() =
|
||||
existingDeclarations.any {
|
||||
missedDeclarations != null && missedDeclarations.none {
|
||||
name == it.name && when (this) {
|
||||
is KtConstructor<*> -> it is KtConstructor<*> && 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>
|
||||
}
|
||||
+5
@@ -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")
|
||||
}
|
||||
}
|
||||
+12
@@ -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.
|
||||
}
|
||||
}
|
||||
+5
@@ -109,6 +109,11 @@ public class QuickFixMultiModuleTestGenerated extends AbstractQuickFixMultiModul
|
||||
runTest("idea/testData/multiModuleQuickFix/classFunction/");
|
||||
}
|
||||
|
||||
@TestMetadata("classFunctionSameSignature")
|
||||
public void testClassFunctionSameSignature() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/classFunctionSameSignature/");
|
||||
}
|
||||
|
||||
@TestMetadata("classFunctionWithConstructor")
|
||||
public void testClassFunctionWithConstructor() throws Exception {
|
||||
runTest("idea/testData/multiModuleQuickFix/classFunctionWithConstructor/");
|
||||
|
||||
Reference in New Issue
Block a user