More correct algorithm for redundant type arguments detection

This commit is contained in:
Valentin Kipyatkov
2017-06-01 00:01:00 +03:00
parent cfa442a42a
commit c08d862e9e
15 changed files with 251 additions and 35 deletions
@@ -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;
@@ -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>()) {
}
@@ -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()
@@ -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
View File
@@ -20,7 +20,7 @@ internal class A {
}
fun bar() {
field5 = ArrayList<String>()
field5 = ArrayList()
field7++
field8++
field9 = null