Allow "add constructor invocation" for sealed top-level inheritors
So #KT-23320 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
d54c57f9df
commit
2d4ef8d1e6
@@ -22,9 +22,7 @@ import com.intellij.codeInsight.intention.IntentionAction
|
|||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
|
||||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
|
||||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
@@ -49,7 +47,7 @@ import org.jetbrains.kotlin.utils.addIfNotNull
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
||||||
private val DISPLAY_MAX_PARAMS = 5
|
private const val DISPLAY_MAX_PARAMS = 5
|
||||||
|
|
||||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||||
val delegator = diagnostic.psiElement as KtSuperTypeEntry
|
val delegator = diagnostic.psiElement as KtSuperTypeEntry
|
||||||
@@ -61,12 +59,21 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
|
|
||||||
val superClass = (type.constructor.declarationDescriptor as? ClassDescriptor) ?: return emptyList()
|
val superClass = (type.constructor.declarationDescriptor as? ClassDescriptor) ?: return emptyList()
|
||||||
val classDescriptor = classOrObjectDeclaration.resolveToDescriptorIfAny(BodyResolveMode.FULL) ?: 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
|
if (constructors.isEmpty()) return emptyList() // no accessible constructor
|
||||||
|
|
||||||
val fixes = ArrayList<IntentionAction>()
|
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) {
|
if (classOrObjectDeclaration is KtClass) {
|
||||||
val superType = classDescriptor.typeConstructor.supertypes.firstOrNull { it.constructor.declarationDescriptor == superClass }
|
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 substitutor = TypeConstructorSubstitution.create(superClass.typeConstructor, superType.arguments).buildSubstitutor()
|
||||||
|
|
||||||
val substitutedConstructors = constructors
|
val substitutedConstructors = constructors
|
||||||
.filter { it.valueParameters.isNotEmpty() }
|
.filter { it.valueParameters.isNotEmpty() }
|
||||||
.mapNotNull { it.substitute(substitutor) }
|
.mapNotNull { it.substitute(substitutor) }
|
||||||
|
|
||||||
if (substitutedConstructors.isNotEmpty()) {
|
if (substitutedConstructors.isNotEmpty()) {
|
||||||
val parameterTypes: List<List<KotlinType>> = substitutedConstructors.map {
|
val parameterTypes: List<List<KotlinType>> = substitutedConstructors.map {
|
||||||
@@ -87,9 +94,8 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
val maxParams = parameterTypes.map { it.size }.max()!!
|
val maxParams = parameterTypes.map { it.size }.max()!!
|
||||||
val maxParamsToDisplay = if (maxParams <= DISPLAY_MAX_PARAMS) {
|
val maxParamsToDisplay = if (maxParams <= DISPLAY_MAX_PARAMS) {
|
||||||
maxParams
|
maxParams
|
||||||
}
|
} else {
|
||||||
else {
|
(DISPLAY_MAX_PARAMS until maxParams).firstOrNull(::canRenderOnlyFirstParameters) ?: maxParams
|
||||||
(DISPLAY_MAX_PARAMS..maxParams-1).firstOrNull(::canRenderOnlyFirstParameters) ?: maxParams
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((constructor, types) in substitutedConstructors.zip(parameterTypes)) {
|
for ((constructor, types) in substitutedConstructors.zip(parameterTypes)) {
|
||||||
@@ -106,8 +112,8 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class AddParenthesisFix(
|
private class AddParenthesisFix(
|
||||||
element: KtSuperTypeEntry,
|
element: KtSuperTypeEntry,
|
||||||
val putCaretIntoParenthesis: Boolean
|
val putCaretIntoParenthesis: Boolean
|
||||||
) : KotlinQuickFixAction<KtSuperTypeEntry>(element), HighPriorityAction {
|
) : KotlinQuickFixAction<KtSuperTypeEntry>(element), HighPriorityAction {
|
||||||
|
|
||||||
override fun getFamilyName() = "Change to constructor invocation" //TODO?
|
override fun getFamilyName() = "Change to constructor invocation" //TODO?
|
||||||
@@ -131,21 +137,21 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class AddParametersFix(
|
private class AddParametersFix(
|
||||||
element: KtSuperTypeEntry,
|
element: KtSuperTypeEntry,
|
||||||
classDeclaration: KtClass,
|
classDeclaration: KtClass,
|
||||||
parametersToAdd: Collection<KtParameter>,
|
parametersToAdd: Collection<KtParameter>,
|
||||||
private val argumentText: String,
|
private val argumentText: String,
|
||||||
private val text: String
|
private val text: String
|
||||||
) : KotlinQuickFixAction<KtSuperTypeEntry>(element) {
|
) : KotlinQuickFixAction<KtSuperTypeEntry>(element) {
|
||||||
private val classDeclarationPointer = classDeclaration.createSmartPointer()
|
private val classDeclarationPointer = classDeclaration.createSmartPointer()
|
||||||
private val parametersToAddPointers = parametersToAdd.map { it.createSmartPointer() }
|
private val parametersToAddPointers = parametersToAdd.map { it.createSmartPointer() }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(
|
fun create(
|
||||||
element: KtSuperTypeEntry,
|
element: KtSuperTypeEntry,
|
||||||
classDeclaration: KtClass,
|
classDeclaration: KtClass,
|
||||||
superConstructor: ConstructorDescriptor,
|
superConstructor: ConstructorDescriptor,
|
||||||
text: String
|
text: String
|
||||||
): AddParametersFix? {
|
): AddParametersFix? {
|
||||||
val superParameters = superConstructor.valueParameters
|
val superParameters = superConstructor.valueParameters
|
||||||
assert(superParameters.isNotEmpty())
|
assert(superParameters.isNotEmpty())
|
||||||
@@ -159,7 +165,7 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
val nameRendered = parameter.name.render()
|
val nameRendered = parameter.name.render()
|
||||||
val varargElementType = parameter.varargElementType
|
val varargElementType = parameter.varargElementType
|
||||||
|
|
||||||
if (argumentText.length > 0) {
|
if (argumentText.isNotEmpty()) {
|
||||||
argumentText.append(", ")
|
argumentText.append(", ")
|
||||||
}
|
}
|
||||||
argumentText.append(if (varargElementType != null) "*$nameRendered" else nameRendered)
|
argumentText.append(if (varargElementType != null) "*$nameRendered" else nameRendered)
|
||||||
@@ -168,7 +174,7 @@ object SuperClassNotInitialized : KotlinIntentionActionsFactory() {
|
|||||||
val existingParameter = oldParameters.firstOrNull { it.name == nameString }
|
val existingParameter = oldParameters.firstOrNull { it.name == nameString }
|
||||||
if (existingParameter != null) {
|
if (existingParameter != null) {
|
||||||
val type = (existingParameter.resolveToParameterDescriptorIfAny() as? VariableDescriptor)?.type
|
val type = (existingParameter.resolveToParameterDescriptorIfAny() as? VariableDescriptor)?.type
|
||||||
?: return null
|
?: return null
|
||||||
if (type.isSubtypeOf(parameter.type)) continue // use existing parameter
|
if (type.isSubtypeOf(parameter.type)) continue // use existing parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -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>
|
||||||
|
}
|
||||||
+9
@@ -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>
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
// "Change to constructor invocation" "true"
|
||||||
|
sealed class A
|
||||||
|
class B : A<caret>
|
||||||
+3
@@ -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);
|
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")
|
@TestMetadata("addParenthesisForLocalClass.kt")
|
||||||
public void testAddParenthesisForLocalClass() throws Exception {
|
public void testAddParenthesisForLocalClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForLocalClass.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/supertypeInitialization/addParenthesisForLocalClass.kt");
|
||||||
@@ -10331,6 +10343,12 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
|||||||
doTest(fileName);
|
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 {
|
public void testAllFilesPresentInSupertypeInitialization() throws Exception {
|
||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/supertypeInitialization"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/supertypeInitialization"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user