More correct algorithm for redundant type arguments detection
This commit is contained in:
+4
-2
@@ -193,7 +193,9 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
|
||||
@Nullable
|
||||
public <T extends PsiElement> T getContextParentOfType(@NotNull KtExpression expression, @NotNull Class<? extends T>... classes) {
|
||||
PsiElement current = expression.getParent();
|
||||
KtExpression context = expressionContextProvider.invoke(expression);
|
||||
PsiElement current = context != null ? context : expression.getParent();
|
||||
|
||||
while (current != null) {
|
||||
for (Class<? extends T> klass : classes) {
|
||||
if (klass.isInstance(current)) {
|
||||
@@ -205,7 +207,7 @@ public abstract class ResolutionContext<Context extends ResolutionContext<Contex
|
||||
if (current instanceof PsiFile) return null;
|
||||
|
||||
if (current instanceof KtExpression) {
|
||||
KtExpression context = expressionContextProvider.invoke((KtExpression) current);
|
||||
context = expressionContextProvider.invoke((KtExpression) current);
|
||||
if (context != null) {
|
||||
current = context;
|
||||
continue;
|
||||
|
||||
+84
-29
@@ -18,17 +18,24 @@ package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.util.Key
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueDescriptor
|
||||
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.copied
|
||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||
import org.jetbrains.kotlin.idea.resolve.frontendService
|
||||
import org.jetbrains.kotlin.idea.project.builtIns
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTraceContext
|
||||
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
|
||||
import org.jetbrains.kotlin.resolve.calls.CallResolver
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
@@ -73,29 +80,33 @@ class RemoveExplicitTypeArgumentsIntention : SelfTargetingOffsetIndependentInten
|
||||
if (callExpression.typeArguments.isEmpty()) return false
|
||||
|
||||
val resolutionFacade = callExpression.getResolutionFacade()
|
||||
val context = resolutionFacade.analyze(callExpression, BodyResolveMode.PARTIAL)
|
||||
val calleeExpression = callExpression.calleeExpression ?: return false
|
||||
val scope = calleeExpression.getResolutionScope(context, resolutionFacade)
|
||||
val originalCall = callExpression.getResolvedCall(context) ?: return false
|
||||
val untypedCall = CallWithoutTypeArgs(originalCall.call)
|
||||
val bindingContext = resolutionFacade.analyze(callExpression, BodyResolveMode.PARTIAL)
|
||||
val originalCall = callExpression.getResolvedCall(bindingContext) ?: return false
|
||||
|
||||
val expectedTypeIsExplicitInCode = callExpression.hasExplicitExpectedType(context)
|
||||
val expectedType = if (expectedTypeIsExplicitInCode) {
|
||||
context[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
else {
|
||||
TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
val dataFlow = context.getDataFlowInfoBefore(callExpression)
|
||||
val callResolver = resolutionFacade.frontendService<CallResolver>()
|
||||
val resolutionResults = callResolver.resolveFunctionCall(
|
||||
BindingTraceContext(), scope, untypedCall, expectedType, dataFlow, false)
|
||||
if (!resolutionResults.isSingleResult) {
|
||||
return false
|
||||
}
|
||||
val (contextExpression, expectedType) = findContextToAnalyze(callExpression, bindingContext)
|
||||
val resolutionScope = contextExpression.getResolutionScope(bindingContext, resolutionFacade)
|
||||
|
||||
val key = Key<Unit>("RemoveExplicitTypeArgumentsIntention")
|
||||
callExpression.putCopyableUserData(key, Unit)
|
||||
val expressionToAnalyze = contextExpression.copied()
|
||||
callExpression.putCopyableUserData(key, null)
|
||||
|
||||
val newCallExpression = expressionToAnalyze.findDescendantOfType<KtCallExpression> { it.getCopyableUserData(key) != null }!!
|
||||
newCallExpression.typeArgumentList!!.delete()
|
||||
|
||||
val newBindingContext = expressionToAnalyze.analyzeInContext(
|
||||
resolutionScope,
|
||||
contextExpression,
|
||||
trace = DelegatingBindingTrace(bindingContext, "Temporary trace"),
|
||||
dataFlowInfo = bindingContext.getDataFlowInfoBefore(contextExpression),
|
||||
expectedType = expectedType ?: TypeUtils.NO_EXPECTED_TYPE,
|
||||
isStatement = expectedType == null
|
||||
)
|
||||
|
||||
val newCall = newCallExpression.getResolvedCall(newBindingContext) ?: return false
|
||||
|
||||
val args = originalCall.typeArguments
|
||||
val newArgs = resolutionResults.resultingCall.typeArguments
|
||||
val newArgs = newCall.typeArguments
|
||||
|
||||
fun equalTypes(type1: KotlinType, type2: KotlinType): Boolean {
|
||||
return if (approximateFlexible) {
|
||||
@@ -110,17 +121,61 @@ class RemoveExplicitTypeArgumentsIntention : SelfTargetingOffsetIndependentInten
|
||||
equalTypes(argType, newArgType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findContextToAnalyze(expression: KtExpression, bindingContext: BindingContext): Pair<KtExpression, KotlinType?> {
|
||||
for (element in expression.parentsWithSelf) {
|
||||
if (element !is KtExpression) continue
|
||||
|
||||
if (element.getQualifiedExpressionForSelector() != null) continue
|
||||
if (!element.isUsedAsExpression(bindingContext)) return element to null
|
||||
|
||||
val parent = element.parent
|
||||
when (parent) {
|
||||
is KtNamedFunction -> {
|
||||
val expectedType = if (element == parent.bodyExpression && !parent.hasBlockBody() && parent.hasDeclaredReturnType())
|
||||
(bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, parent] as? FunctionDescriptor)?.returnType
|
||||
else
|
||||
null
|
||||
return element to expectedType
|
||||
}
|
||||
|
||||
is KtVariableDeclaration -> {
|
||||
val expectedType = if (element == parent.initializer && parent.typeReference != null)
|
||||
(bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, parent] as? ValueDescriptor)?.type
|
||||
else
|
||||
null
|
||||
return element to expectedType
|
||||
}
|
||||
|
||||
is KtParameter -> {
|
||||
val expectedType = if (element == parent.defaultValue)
|
||||
(bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, parent] as? ValueDescriptor)?.type
|
||||
else
|
||||
null
|
||||
return element to expectedType
|
||||
}
|
||||
|
||||
is KtPropertyAccessor -> {
|
||||
val property = parent.parent as KtProperty
|
||||
val expectedType = when {
|
||||
element != parent.bodyExpression || parent.hasBlockBody() -> null
|
||||
parent.isSetter -> parent.builtIns.unitType
|
||||
property.typeReference == null -> null
|
||||
else -> (bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, parent] as? FunctionDescriptor)?.returnType
|
||||
}
|
||||
return element to expectedType
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expression to null
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicableTo(element: KtTypeArgumentList): Boolean {
|
||||
return isApplicableTo(element, approximateFlexible = false)
|
||||
}
|
||||
|
||||
private class CallWithoutTypeArgs(call: Call) : DelegatingCall(call) {
|
||||
override fun getTypeArguments() = emptyList<KtTypeProjection>()
|
||||
override fun getTypeArgumentList() = null
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtTypeArgumentList, editor: Editor?) {
|
||||
element.delete()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun f(p: Int): List<String>? {
|
||||
return if (p > 0) {
|
||||
print("a")
|
||||
listOf<caret><String>()
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun f(p: Int): List<String>? {
|
||||
return if (p > 0) {
|
||||
print("a")
|
||||
listOf()
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo(p: List<String> = listOf<caret><String>()) {
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo(p: List<String> = listOf()) {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x: List<String>
|
||||
get() = listOf<caret><String>()
|
||||
@@ -0,0 +1,5 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
val x: List<String>
|
||||
get() = listOf()
|
||||
+56
-3
@@ -138,7 +138,7 @@
|
||||
<file>insideOtherCall.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/insideOtherCall.kt" />
|
||||
<entry_point TYPE="file" FQNAME="insideOtherCall.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -147,7 +147,7 @@
|
||||
<file>insideDeepOtherCall.kt</file>
|
||||
<line>8</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/insideDeepOtherCall.kt" />
|
||||
<entry_point TYPE="file" FQNAME="insideDeepOtherCall.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -156,9 +156,62 @@
|
||||
<file>insideDeepOtherCall.kt</file>
|
||||
<line>8</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/insideDeepOtherCall.kt" />
|
||||
<entry_point TYPE="file" FQNAME="insideDeepOtherCall.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>twoArguments.kt</file>
|
||||
<line>8</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="twoArguments.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>twoArguments.kt</file>
|
||||
<line>8</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="twoArguments.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>qualified.kt</file>
|
||||
<line>6</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="qualified.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>getterBody.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="getterBody.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>defaultParamValue.kt</file>
|
||||
<line>4</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="defaultParamValue.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>blockValue.kt</file>
|
||||
<line>7</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="blockValue.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Unnecessary type argument</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
</problems>
|
||||
@@ -0,0 +1,7 @@
|
||||
interface I<T>
|
||||
|
||||
fun <T> Int.foo(p: I<T>){}
|
||||
|
||||
fun bar(p: I<String>) {
|
||||
1.foo<caret><String>(p)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
interface I<T>
|
||||
|
||||
fun <T> Int.foo(p: I<T>){}
|
||||
|
||||
fun bar(p: I<String>) {
|
||||
1.foo(p)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun <T> foo(p1: List<T>, p2: List<T>) {
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
foo(listOf<caret><String>(), listOf<String>())
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// IS_APPLICABLE: true
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun <T> foo(p1: List<T>, p2: List<T>) {
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
foo(listOf(), listOf<String>())
|
||||
}
|
||||
@@ -12896,6 +12896,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/removeExplicitTypeArguments"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("blockValue.kt")
|
||||
public void testBlockValue() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/blockValue.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultParamValue.kt")
|
||||
public void testDefaultParamValue() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/defaultParamValue.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("fourLiterals.kt")
|
||||
public void testFourLiterals() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/fourLiterals.kt");
|
||||
@@ -12908,6 +12920,12 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("getterBody.kt")
|
||||
public void testGetterBody() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/getterBody.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inapplicableTypeThatIsAFunItCannotBeInferred.kt")
|
||||
public void testInapplicableTypeThatIsAFunItCannotBeInferred() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/inapplicableTypeThatIsAFunItCannotBeInferred.kt");
|
||||
@@ -13010,12 +13028,24 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("qualified.kt")
|
||||
public void testQualified() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/qualified.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnCallWithUnnecessaryTypeArgs.kt")
|
||||
public void testReturnCallWithUnnecessaryTypeArgs() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/returnCallWithUnnecessaryTypeArgs.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("twoArguments.kt")
|
||||
public void testTwoArguments() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/twoArguments.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("twoLiteralValues.kt")
|
||||
public void testTwoLiteralValues() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/twoLiteralValues.kt");
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ internal class A {
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
field5 = ArrayList<String>()
|
||||
field5 = ArrayList()
|
||||
field7++
|
||||
field8++
|
||||
field9 = null
|
||||
|
||||
Reference in New Issue
Block a user