KT-7989 ReplaceWith replacement adds redundant type arguments for platform types
Highlighting of redundant type arguments made more strict about platform types #KT-7989 Fixed
This commit is contained in:
+55
-38
@@ -31,7 +31,9 @@ import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfo
|
||||
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.JetType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.checker.JetTypeChecker
|
||||
|
||||
public class RemoveExplicitTypeArgumentsInspection : IntentionBasedInspection<JetTypeArgumentList>(RemoveExplicitTypeArgumentsIntention()) {
|
||||
override val problemHighlightType: ProblemHighlightType
|
||||
@@ -39,45 +41,60 @@ public class RemoveExplicitTypeArgumentsInspection : IntentionBasedInspection<Je
|
||||
}
|
||||
|
||||
public class RemoveExplicitTypeArgumentsIntention : JetSelfTargetingOffsetIndependentIntention<JetTypeArgumentList>(javaClass(), "Remove explicit type arguments") {
|
||||
companion object {
|
||||
public fun isApplicableTo(element: JetTypeArgumentList, approximateFlexible: Boolean): Boolean {
|
||||
val callExpression = element.getParent() as? JetCallExpression ?: return false
|
||||
if (callExpression.getTypeArguments().isEmpty()) return false
|
||||
|
||||
val context = callExpression.analyze(BodyResolveMode.PARTIAL)
|
||||
val calleeExpression = callExpression.getCalleeExpression() ?: return false
|
||||
val scope = context[BindingContext.RESOLUTION_SCOPE, calleeExpression/*TODO: discuss it*/] ?: return false
|
||||
val originalCall = callExpression.getResolvedCall(context) ?: return false
|
||||
val untypedCall = CallWithoutTypeArgs(originalCall.getCall())
|
||||
|
||||
// todo Check with expected type for other expressions
|
||||
// If always use expected type from trace there is a problem with nested calls:
|
||||
// the expression type for them can depend on their explicit type arguments (via outer call),
|
||||
// therefore we should resolve outer call with erased type arguments for inner call
|
||||
val parent = callExpression.getParent()
|
||||
val expectedTypeIsExplicitInCode = when (parent) {
|
||||
is JetProperty -> parent.getInitializer() == callExpression && parent.getTypeReference() != null
|
||||
is JetDeclarationWithBody -> parent.getBodyExpression() == callExpression
|
||||
is JetReturnExpression -> true
|
||||
else -> false
|
||||
}
|
||||
val expectedType = if (expectedTypeIsExplicitInCode) {
|
||||
context[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
else {
|
||||
TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
val dataFlow = context.getDataFlowInfo(callExpression)
|
||||
val injector = InjectorForMacros(callExpression.getProject(), callExpression.findModuleDescriptor())
|
||||
val resolutionResults = injector.getCallResolver().resolveFunctionCall(
|
||||
BindingTraceContext(), scope, untypedCall, expectedType, dataFlow, false)
|
||||
assert (resolutionResults.isSingleResult()) {
|
||||
"Removing type arguments changed resolve for: ${callExpression.getTextWithLocation()} to ${resolutionResults.getResultCode()}"
|
||||
}
|
||||
|
||||
val args = originalCall.getTypeArguments()
|
||||
val newArgs = resolutionResults.getResultingCall().getTypeArguments()
|
||||
|
||||
fun equalTypes(type1: JetType, type2: JetType): Boolean {
|
||||
return if (approximateFlexible) {
|
||||
JetTypeChecker.DEFAULT.equalTypes(type1, type2)
|
||||
}
|
||||
else {
|
||||
type1 == type2
|
||||
}
|
||||
}
|
||||
|
||||
return args.size() == newArgs.size() && args.values().zip(newArgs.values()).all { pair -> equalTypes(pair.first, pair.second) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicableTo(element: JetTypeArgumentList): Boolean {
|
||||
val callExpression = element.getParent() as? JetCallExpression ?: return false
|
||||
if (callExpression.getTypeArguments().isEmpty()) return false
|
||||
|
||||
val context = callExpression.analyze(BodyResolveMode.PARTIAL)
|
||||
val calleeExpression = callExpression.getCalleeExpression() ?: return false
|
||||
val scope = context[BindingContext.RESOLUTION_SCOPE, calleeExpression/*TODO: discuss it*/] ?: return false
|
||||
val originalCall = callExpression.getResolvedCall(context) ?: return false
|
||||
val untypedCall = CallWithoutTypeArgs(originalCall.getCall())
|
||||
|
||||
// todo Check with expected type for other expressions
|
||||
// If always use expected type from trace there is a problem with nested calls:
|
||||
// the expression type for them can depend on their explicit type arguments (via outer call),
|
||||
// therefore we should resolve outer call with erased type arguments for inner call
|
||||
val parent = callExpression.getParent()
|
||||
val expectedTypeIsExplicitInCode = when (parent) {
|
||||
is JetProperty -> parent.getInitializer() == callExpression && parent.getTypeReference() != null
|
||||
is JetDeclarationWithBody -> parent.getBodyExpression() == callExpression
|
||||
is JetReturnExpression -> true
|
||||
else -> false
|
||||
}
|
||||
val expectedType = if (expectedTypeIsExplicitInCode) {
|
||||
context[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
else {
|
||||
TypeUtils.NO_EXPECTED_TYPE
|
||||
}
|
||||
val dataFlow = context.getDataFlowInfo(callExpression)
|
||||
val injector = InjectorForMacros(callExpression.getProject(), callExpression.findModuleDescriptor())
|
||||
val resolutionResults = injector.getCallResolver().resolveFunctionCall(
|
||||
BindingTraceContext(), scope, untypedCall, expectedType, dataFlow, false)
|
||||
assert (resolutionResults.isSingleResult()) {
|
||||
"Removing type arguments changed resolve for: ${callExpression.getTextWithLocation()} to ${resolutionResults.getResultCode()}"
|
||||
}
|
||||
|
||||
val args = originalCall.getTypeArguments()
|
||||
val newArgs = resolutionResults.getResultingCall().getTypeArguments()
|
||||
|
||||
return args == newArgs.mapValues { approximateFlexibleTypes(it.getValue(), false) }
|
||||
return isApplicableTo(element, approximateFlexible = false)
|
||||
}
|
||||
|
||||
private class CallWithoutTypeArgs(call: Call) : DelegatingCall(call) {
|
||||
|
||||
@@ -111,7 +111,7 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
|
||||
}
|
||||
|
||||
override fun visitTypeArgumentList(typeArgumentList: JetTypeArgumentList) {
|
||||
if (rangeFilter(typeArgumentList) == RangeFilterResult.PROCESS && RemoveExplicitTypeArgumentsIntention().isApplicableTo(typeArgumentList)) {
|
||||
if (rangeFilter(typeArgumentList) == RangeFilterResult.PROCESS && RemoveExplicitTypeArgumentsIntention.isApplicableTo(typeArgumentList, approximateFlexible = true)) {
|
||||
redundantTypeArgs.add(typeArgumentList)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -541,9 +541,9 @@ public abstract class DeprecatedSymbolUsageFixBase(
|
||||
}
|
||||
|
||||
private fun removeExplicitTypeArguments(result: JetExpression) {
|
||||
val intention = RemoveExplicitTypeArgumentsIntention()
|
||||
result.collectDescendantsOfType<JetTypeArgumentList>(canGoInside = { !it[USER_CODE_KEY] }) { intention.isApplicableTo(it) }
|
||||
.forEach { it.delete() }
|
||||
result.collectDescendantsOfType<JetTypeArgumentList>(canGoInside = { !it[USER_CODE_KEY] }) {
|
||||
RemoveExplicitTypeArgumentsIntention.isApplicableTo(it, approximateFlexible = true)
|
||||
}.forEach { it.delete() }
|
||||
}
|
||||
|
||||
private fun simplifySpreadArrayOfArguments(expression: JetExpression) {
|
||||
|
||||
+34
-15
@@ -3,7 +3,7 @@
|
||||
<file>variableStringFartherScope.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/variableStringFartherScope.kt" />
|
||||
<entry_point TYPE="file" FQNAME="variableStringFartherScope.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -12,7 +12,7 @@
|
||||
<file>fourLiterals.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/fourLiterals.kt" />
|
||||
<entry_point TYPE="file" FQNAME="fourLiterals.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -21,7 +21,7 @@
|
||||
<file>literalStringWithClass.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/literalStringWithClass.kt" />
|
||||
<entry_point TYPE="file" FQNAME="literalStringWithClass.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -30,7 +30,7 @@
|
||||
<file>lambdaType.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/lambdaType.kt" />
|
||||
<entry_point TYPE="file" FQNAME="lambdaType.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -39,7 +39,7 @@
|
||||
<file>literalAny.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/literalAny.kt" />
|
||||
<entry_point TYPE="file" FQNAME="literalAny.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -48,7 +48,7 @@
|
||||
<file>literalString.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/literalString.kt" />
|
||||
<entry_point TYPE="file" FQNAME="literalString.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -57,7 +57,7 @@
|
||||
<file>variablesAndLiterals.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/variablesAndLiterals.kt" />
|
||||
<entry_point TYPE="file" FQNAME="variablesAndLiterals.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -66,7 +66,7 @@
|
||||
<file>literalsWhenTypeArgHasTypeArg.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/literalsWhenTypeArgHasTypeArg.kt" />
|
||||
<entry_point TYPE="file" FQNAME="literalsWhenTypeArgHasTypeArg.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -75,7 +75,7 @@
|
||||
<file>variableString.kt</file>
|
||||
<line>4</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/variableString.kt" />
|
||||
<entry_point TYPE="file" FQNAME="variableString.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -84,7 +84,7 @@
|
||||
<file>variableString2.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/variableString2.kt" />
|
||||
<entry_point TYPE="file" FQNAME="variableString2.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -93,7 +93,7 @@
|
||||
<file>twoLiteralValues.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/twoLiteralValues.kt" />
|
||||
<entry_point TYPE="file" FQNAME="twoLiteralValues.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -102,7 +102,7 @@
|
||||
<file>returnCallWithUnnecessaryTypeArgs.kt</file>
|
||||
<line>4</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/returnCallWithUnnecessaryTypeArgs.kt" />
|
||||
<entry_point TYPE="file" FQNAME="returnCallWithUnnecessaryTypeArgs.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -111,7 +111,7 @@
|
||||
<file>propertyInitializerIsCallWithUnnecessaryTypeArgs.kt</file>
|
||||
<line>4</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/propertyInitializerIsCallWithUnnecessaryTypeArgs.kt" />
|
||||
<entry_point TYPE="file" FQNAME="propertyInitializerIsCallWithUnnecessaryTypeArgs.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -120,7 +120,7 @@
|
||||
<file>functionBodyIsCallWithUnnecessaryTypeArgs.kt</file>
|
||||
<line>3</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/functionBodyIsCallWithUnnecessaryTypeArgs.kt" />
|
||||
<entry_point TYPE="file" FQNAME="functionBodyIsCallWithUnnecessaryTypeArgs.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
@@ -129,7 +129,26 @@
|
||||
<file>mapGet.kt</file>
|
||||
<line>9</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/mapGet.kt" />
|
||||
<entry_point TYPE="file" FQNAME="mapGet.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
<problem>
|
||||
<file>platforrmType2.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="platforrmType2.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
|
||||
<problem>
|
||||
<file>platforrmType1.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="platforrmType1.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Type arguments are unnecessary</problem_class>
|
||||
<description>Remove explicit type arguments</description>
|
||||
</problem>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.util.List;
|
||||
|
||||
class JavaClass {
|
||||
static List<String> method(){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IS_APPLICABLE: false
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
JavaClass.method().toTypedArray<caret><String>()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.util.List;
|
||||
|
||||
class JavaClass {
|
||||
static List<String> method(){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// IS_APPLICABLE: false
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
JavaClass.method().toTypedArray<caret><String?>()
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import java.util.List;
|
||||
|
||||
class JavaClass {
|
||||
static List<String> list() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'newFun()'" "true"
|
||||
|
||||
@deprecated("", ReplaceWith("newFun()"))
|
||||
fun <T> Collection<T>.oldFun() {}
|
||||
|
||||
fun <T> Collection<T>.newFun() {}
|
||||
|
||||
fun foo() {
|
||||
JavaClass.list().<caret>newFun()
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import java.util.List;
|
||||
|
||||
class JavaClass {
|
||||
static List<String> list() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// "Replace with 'newFun()'" "true"
|
||||
|
||||
@deprecated("", ReplaceWith("newFun()"))
|
||||
fun <T> Collection<T>.oldFun() {}
|
||||
|
||||
fun <T> Collection<T>.newFun() {}
|
||||
|
||||
fun foo() {
|
||||
JavaClass.list().<caret>oldFun()
|
||||
}
|
||||
@@ -5813,6 +5813,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("platforrmType1.kt")
|
||||
public void testPlatforrmType1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/platforrmType1.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("platforrmType2.kt")
|
||||
public void testPlatforrmType2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/platforrmType2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyInitializerIsCallWithUnnecessaryTypeArgs.kt")
|
||||
public void testPropertyInitializerIsCallWithUnnecessaryTypeArgs() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/removeExplicitTypeArguments/propertyInitializerIsCallWithUnnecessaryTypeArgs.kt");
|
||||
|
||||
@@ -903,6 +903,21 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class TypeArguments extends AbstractQuickFixMultiFileTest {
|
||||
public void testAllFilesPresentInTypeArguments() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments"), Pattern.compile("^(\\w+)\\.before\\.Main\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("platformType.before.Main.kt")
|
||||
public void testPlatformType() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/quickfix/deprecatedSymbolUsage/typeArguments/platformType.before.Main.kt");
|
||||
doTestWithExtraFile(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/deprecatedSymbolUsage/wholeProject")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
@@ -3,6 +3,6 @@ import javaApi.T
|
||||
|
||||
class A {
|
||||
public fun foo(t: T): Any {
|
||||
return Collections.nCopies<Set<String>>(1, t.set)
|
||||
return Collections.nCopies(1, t.set)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user