Implement quckfix adding explicit upper bounds for generic when needed
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
package org.jetbrains.kotlin.psi;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.psi.stubs.KotlinTypeParameterStub;
|
||||
@@ -56,6 +58,27 @@ public class JetTypeParameter extends JetNamedDeclarationStub<KotlinTypeParamete
|
||||
return Variance.INVARIANT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JetTypeReference setExtendsBound(@Nullable JetTypeReference typeReference) {
|
||||
JetTypeReference currentExtendsBound = getExtendsBound();
|
||||
if (currentExtendsBound != null) {
|
||||
if (typeReference == null) {
|
||||
PsiElement colon = findChildByType(JetTokens.COLON);
|
||||
if (colon != null) colon.delete();
|
||||
currentExtendsBound.delete();
|
||||
return null;
|
||||
}
|
||||
return (JetTypeReference) currentExtendsBound.replace(typeReference);
|
||||
}
|
||||
|
||||
if (typeReference != null) {
|
||||
PsiElement colon = addAfter(new JetPsiFactory(getProject()).createColon(), getNameIdentifier());
|
||||
return (JetTypeReference) addAfter(typeReference, colon);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JetTypeReference getExtendsBound() {
|
||||
return getStubOrPsiChild(JetStubElementTypes.TYPE_REFERENCE);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
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.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||
import org.jetbrains.kotlin.psi.JetFile
|
||||
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||
import org.jetbrains.kotlin.psi.JetTypeParameter
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemImpl
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintsUtil
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.InferenceErrorData
|
||||
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.utils.singletonOrEmptyList
|
||||
|
||||
public class AddGenericUpperBoundFix(
|
||||
typeParameter: JetTypeParameter,
|
||||
upperBound: JetType
|
||||
) : JetIntentionAction<JetTypeParameter>(typeParameter) {
|
||||
private val renderedUpperBound: String = IdeDescriptorRenderers.SOURCE_CODE.renderType(upperBound)
|
||||
|
||||
override fun getText() = "Add '$renderedUpperBound' as upper bound for ${element.name}"
|
||||
override fun getFamilyName() = "Add generic upper bound"
|
||||
|
||||
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile): Boolean {
|
||||
if (!super.isAvailable(project, editor, file)) return false
|
||||
// TODO: replacing existing upper bounds
|
||||
return (element.name != null && element.extendsBound == null)
|
||||
}
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: JetFile) {
|
||||
assert(element.extendsBound == null, "Don't know what to do with existing bounds")
|
||||
|
||||
val typeReference = JetPsiFactory(project).createType(renderedUpperBound)
|
||||
val insertedTypeReference = element.setExtendsBound(typeReference)!!
|
||||
|
||||
ShortenReferences.DEFAULT.process(insertedTypeReference)
|
||||
}
|
||||
|
||||
companion object Factory : JetIntentionActionsFactory() {
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
return when (diagnostic.factory) {
|
||||
Errors.UPPER_BOUND_VIOLATED -> {
|
||||
val upperBoundViolated = Errors.UPPER_BOUND_VIOLATED.cast(diagnostic)
|
||||
createAction(upperBoundViolated.b, upperBoundViolated.a).singletonOrEmptyList()
|
||||
}
|
||||
Errors.TYPE_INFERENCE_UPPER_BOUND_VIOLATED -> {
|
||||
val inferenceData = Errors.TYPE_INFERENCE_UPPER_BOUND_VIOLATED.cast(diagnostic).a
|
||||
createActionsByInferenceData(inferenceData)
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActionsByInferenceData(inferenceData: InferenceErrorData): List<IntentionAction> {
|
||||
val successfulConstraintSystem = (inferenceData.constraintSystem as? ConstraintSystemImpl)
|
||||
?.filterConstraintsOut(ConstraintPositionKind.TYPE_BOUND_POSITION)
|
||||
?: return emptyList()
|
||||
|
||||
if (!successfulConstraintSystem.getStatus().isSuccessful()) return emptyList()
|
||||
|
||||
val resultingSubstitutor = successfulConstraintSystem.getResultingSubstitutor()
|
||||
|
||||
return inferenceData.descriptor.typeParameters.map factory@{
|
||||
typeParameterDescriptor ->
|
||||
|
||||
if (ConstraintsUtil.checkUpperBoundIsSatisfied(
|
||||
successfulConstraintSystem, typeParameterDescriptor, /* substituteOtherTypeParametersInBound */ true
|
||||
)) return@factory null
|
||||
|
||||
val upperBound = typeParameterDescriptor.upperBounds.singleOrNull() ?: return@factory null
|
||||
val argument = resultingSubstitutor.substitute(typeParameterDescriptor.defaultType, Variance.INVARIANT)
|
||||
?: return@factory null
|
||||
|
||||
createAction(argument, upperBound)
|
||||
}.filterNotNull()
|
||||
}
|
||||
|
||||
private fun createAction(argument: JetType, upperBound: JetType): IntentionAction? {
|
||||
if (!upperBound.constructor.isDenotable) return null
|
||||
|
||||
val typeParameterDescriptor = (argument.constructor.declarationDescriptor as? TypeParameterDescriptor) ?: return null
|
||||
val typeParameterDeclaration =
|
||||
(DescriptorToSourceUtils.getSourceFromDescriptor(typeParameterDescriptor) as? JetTypeParameter) ?: return null
|
||||
|
||||
return AddGenericUpperBoundFix(typeParameterDeclaration, upperBound)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,5 +318,8 @@ public class QuickFixRegistrar : QuickFixContributor {
|
||||
NO_REFLECTION_IN_CLASS_PATH.registerFactory(AddReflectionQuickFix)
|
||||
|
||||
ErrorsJvm.JAVA_TYPE_MISMATCH.registerFactory(CastExpressionFix.createFactoryForGenericVarianceConversion())
|
||||
|
||||
UPPER_BOUND_VIOLATED.registerFactory(AddGenericUpperBoundFix.Factory)
|
||||
TYPE_INFERENCE_UPPER_BOUND_VIOLATED.registerFactory(AddGenericUpperBoundFix.Factory)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
fun <T : Any> foo() = 1
|
||||
|
||||
fun <E> bar() = foo<E<caret>>()
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
fun <T : Any> foo() = 1
|
||||
|
||||
fun <E : Any> bar() = foo<E>()
|
||||
@@ -0,0 +1,6 @@
|
||||
// "class org.jetbrains.kotlin.idea.quickfix.AddGenericUpperBoundFix" "false"
|
||||
// ERROR: <html>Type argument is not within its bounds.<table><tr><td>Expected:</td><td>kotlin.Any</td></tr><tr><td>Found:</td><td>E</td></tr></table></html>
|
||||
|
||||
fun <T : Any> foo() = 1
|
||||
|
||||
fun <E : Any?> bar() = foo<E<caret>>()
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
// ERROR: <html>Type parameter bound for <b>U</b> in <table><tr><td width="10%"></td><td align="right" colspan="2" style="white-space:nowrap;font-weight:bold;"><b>fun</b> <T : kotlin.Any, U : kotlin.Any> foo</td><td style="white-space:nowrap;font-weight:bold;">(</td><td align="right" style="white-space:nowrap;font-weight:bold;">x: T,</td><td align="right" style="white-space:nowrap;font-weight:bold;">y: U</td><td style="white-space:nowrap;font-weight:bold;">)</td><td style="white-space:nowrap;font-weight:bold;">: kotlin.Int</td></tr></table> is not satisfied: inferred type <font color=red><b>F</b></font> is not a subtype of <b>kotlin.Any</b></html>
|
||||
|
||||
fun <T : Any, U: Any> foo(x: T, y: U) = 1
|
||||
|
||||
fun <E, F> bar(x: E, y: F) = <caret>foo(x, y)
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
// ERROR: <html>Type parameter bound for <b>U</b> in <table><tr><td width="10%"></td><td align="right" colspan="2" style="white-space:nowrap;font-weight:bold;"><b>fun</b> <T : kotlin.Any, U : kotlin.Any> foo</td><td style="white-space:nowrap;font-weight:bold;">(</td><td align="right" style="white-space:nowrap;font-weight:bold;">x: T,</td><td align="right" style="white-space:nowrap;font-weight:bold;">y: U</td><td style="white-space:nowrap;font-weight:bold;">)</td><td style="white-space:nowrap;font-weight:bold;">: kotlin.Int</td></tr></table> is not satisfied: inferred type <font color=red><b>F</b></font> is not a subtype of <b>kotlin.Any</b></html>
|
||||
|
||||
fun <T : Any, U: Any> foo(x: T, y: U) = 1
|
||||
|
||||
fun <E : Any, F> bar(x: E, y: F) = <caret>foo(x, y)
|
||||
@@ -0,0 +1,3 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
inline fun <reified /* abc */ E> bar() = E::class.java<caret>
|
||||
@@ -0,0 +1,3 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
inline fun <reified /* abc */ E : Any> bar() = E::class.java
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add 'E' as upper bound for F" "true"
|
||||
|
||||
fun <T, U : T> foo() = 1
|
||||
|
||||
fun <E, F> bar() = foo<E, F<caret>>()
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Add 'E' as upper bound for F" "true"
|
||||
|
||||
fun <T, U : T> foo() = 1
|
||||
|
||||
fun <E, F : E> bar() = foo<E, F<caret>>()
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
class A<T : Any>
|
||||
fun <E> bar(x: A<E<caret>>) {}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Add 'kotlin.Any' as upper bound for E" "true"
|
||||
|
||||
class A<T : Any>
|
||||
fun <E : Any> bar(x: A<E<caret>>) {}
|
||||
@@ -176,6 +176,51 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addGenericUpperBound")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class AddGenericUpperBound extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInAddGenericUpperBound() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/addGenericUpperBound"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("basic.kt")
|
||||
public void testBasic() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/basic.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("boundAlreadyExists.kt")
|
||||
public void testBoundAlreadyExists() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/boundAlreadyExists.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inferenceTwoParams.kt")
|
||||
public void testInferenceTwoParams() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/inferenceTwoParams.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kClassRuntime.kt")
|
||||
public void testKClassRuntime() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/kClassRuntime.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("paramAsBound.kt")
|
||||
public void testParamAsBound() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/paramAsBound.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("withinDeclaration.kt")
|
||||
public void testWithinDeclaration() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/addGenericUpperBound/withinDeclaration.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/addStarProjections")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user