Allow "add constructor invocation" for sealed top-level inheritors

So #KT-23320 Fixed
This commit is contained in:
Toshiaki Kameyama
2018-03-24 08:30:39 +03:00
committed by Mikhail Glukhikh
parent d54c57f9df
commit 2d4ef8d1e6
6 changed files with 71 additions and 24 deletions
@@ -22,9 +22,7 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
@@ -49,7 +47,7 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.*
object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
private val DISPLAY_MAX_PARAMS = 5
private const val DISPLAY_MAX_PARAMS = 5
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
val delegator = diagnostic.psiElement as KtSuperTypeEntry
@@ -61,12 +59,21 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
val superClass = (type.constructor.declarationDescriptor as? ClassDescriptor) ?: return emptyList()
val classDescriptor = classOrObjectDeclaration.resolveToDescriptorIfAny(BodyResolveMode.FULL) ?: return emptyList()
val constructors = superClass.constructors.filter { it.isVisible(classDescriptor) }
val containingPackage = superClass.containingDeclaration as? PackageFragmentDescriptor
val inSameFile = containingPackage == classDescriptor.containingDeclaration
val constructors = superClass.constructors.filter {
it.isVisible(classDescriptor) || (superClass.modality == Modality.SEALED && inSameFile)
}
if (constructors.isEmpty()) return emptyList() // no accessible constructor
val fixes = ArrayList<IntentionAction>()
fixes.add(AddParenthesisFix(delegator, putCaretIntoParenthesis = constructors.singleOrNull()?.valueParameters?.isNotEmpty() ?: true))
fixes.add(
AddParenthesisFix(
delegator,
putCaretIntoParenthesis = constructors.singleOrNull()?.valueParameters?.isNotEmpty() ?: true
)
)
if (classOrObjectDeclaration is KtClass) {
val superType = classDescriptor.typeConstructor.supertypes.firstOrNull { it.constructor.declarationDescriptor == superClass }
@@ -74,8 +81,8 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
val substitutor = TypeConstructorSubstitution.create(superClass.typeConstructor, superType.arguments).buildSubstitutor()
val substitutedConstructors = constructors
.filter { it.valueParameters.isNotEmpty() }
.mapNotNull { it.substitute(substitutor) }
.filter { it.valueParameters.isNotEmpty() }
.mapNotNull { it.substitute(substitutor) }
if (substitutedConstructors.isNotEmpty()) {
val parameterTypes: List<List<KotlinType>> = substitutedConstructors.map {
@@ -87,9 +94,8 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
val maxParams = parameterTypes.map { it.size }.max()!!
val maxParamsToDisplay = if (maxParams <= DISPLAY_MAX_PARAMS) {
maxParams
}
else {
(DISPLAY_MAX_PARAMS..maxParams-1).firstOrNull(::canRenderOnlyFirstParameters) ?: maxParams
} else {
(DISPLAY_MAX_PARAMS until maxParams).firstOrNull(::canRenderOnlyFirstParameters) ?: maxParams
}
for ((constructor, types) in substitutedConstructors.zip(parameterTypes)) {
@@ -106,8 +112,8 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
}
private class AddParenthesisFix(
element: KtSuperTypeEntry,
val putCaretIntoParenthesis: Boolean
element: KtSuperTypeEntry,
val putCaretIntoParenthesis: Boolean
) : KotlinQuickFixAction<KtSuperTypeEntry>(element), HighPriorityAction {
override fun getFamilyName() = "Change to constructor invocation" //TODO?
@@ -131,21 +137,21 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
}
private class AddParametersFix(
element: KtSuperTypeEntry,
classDeclaration: KtClass,
parametersToAdd: Collection<KtParameter>,
private val argumentText: String,
private val text: String
element: KtSuperTypeEntry,
classDeclaration: KtClass,
parametersToAdd: Collection<KtParameter>,
private val argumentText: String,
private val text: String
) : KotlinQuickFixAction<KtSuperTypeEntry>(element) {
private val classDeclarationPointer = classDeclaration.createSmartPointer()
private val parametersToAddPointers = parametersToAdd.map { it.createSmartPointer() }
companion object {
fun create(
element: KtSuperTypeEntry,
classDeclaration: KtClass,
superConstructor: ConstructorDescriptor,
text: String
element: KtSuperTypeEntry,
classDeclaration: KtClass,
superConstructor: ConstructorDescriptor,
text: String
): AddParametersFix? {
val superParameters = superConstructor.valueParameters
assert(superParameters.isNotEmpty())
@@ -159,7 +165,7 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
val nameRendered = parameter.name.render()
val varargElementType = parameter.varargElementType
if (argumentText.length > 0) {
if (argumentText.isNotEmpty()) {
argumentText.append(", ")
}
argumentText.append(if (varargElementType != null) "*$nameRendered" else nameRendered)
@@ -168,7 +174,7 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
val existingParameter = oldParameters.firstOrNull { it.name == nameString }
if (existingParameter != null) {
val type = (existingParameter.resolveToParameterDescriptorIfAny() as? VariableDescriptor)?.type
?: return null
?: return null
if (type.isSubtypeOf(parameter.type)) continue // use existing parameter
}
@@ -0,0 +1,8 @@
// "Change to constructor invocation" "false"
// ERROR: This type has a constructor, and thus must be initialized here
// ERROR: This type is sealed, so it can be inherited by only its own nested classes or objects
sealed class A
fun test() {
class B : A<caret>
}
@@ -0,0 +1,9 @@
// "Change to constructor invocation" "false"
// ERROR: This type has a constructor, and thus must be initialized here
// ERROR: This type is sealed, so it can be inherited by only its own nested classes or objects
class My {
sealed class A
class B : A<caret>
}
@@ -0,0 +1,3 @@
// "Change to constructor invocation" "true"
sealed class A
class B : A<caret>
@@ -0,0 +1,3 @@
// "Change to constructor invocation" "true"
sealed class A
class B : A<caret>()
@@ -10319,6 +10319,18 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("addParenthesisForInvalidSealedClass.kt")
public void testAddParenthesisForInvalidSealedClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForInvalidSealedClass.kt");
doTest(fileName);
}
@TestMetadata("addParenthesisForInvalidSealedClass2.kt")
public void testAddParenthesisForInvalidSealedClass2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForInvalidSealedClass2.kt");
doTest(fileName);
}
@TestMetadata("addParenthesisForLocalClass.kt")
public void testAddParenthesisForLocalClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForLocalClass.kt");
@@ -10331,6 +10343,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
doTest(fileName);
}
@TestMetadata("addParenthesisForSealedClass.kt")
public void testAddParenthesisForSealedClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForSealedClass.kt");
doTest(fileName);
}
public void testAllFilesPresentInSupertypeInitialization() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/supertypeInitialization"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}