Implement quckfix adding explicit upper bounds for generic when needed

This commit is contained in:
Denis Zharkov
2015-08-31 17:42:56 +03:00
parent c17451cf5c
commit c59b118b09
15 changed files with 236 additions and 0 deletions
@@ -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)
}
}
+5
View File
@@ -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> &lt;T : kotlin.Any, U : kotlin.Any&gt; 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> &lt;T : kotlin.Any, U : kotlin.Any&gt; 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)