UsePropertyAccessSyntaxIntention: handled case when property type is more specific
This commit is contained in:
@@ -17,11 +17,18 @@
|
||||
package org.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import org.jetbrains.kotlin.analyzer.analyzeInContext
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.frontend.di.createContainerForMacros
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.copied
|
||||
import org.jetbrains.kotlin.idea.core.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
@@ -36,10 +43,13 @@ import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
|
||||
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
|
||||
import org.jetbrains.kotlin.resolve.calls.context.CheckArgumentTypesMode
|
||||
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.calls.util.DelegatingCall
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.JetScope
|
||||
import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.types.JetType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
|
||||
class UsePropertyAccessSyntaxInspection : IntentionBasedInspection<JetCallExpression>(UsePropertyAccessSyntaxIntention())
|
||||
@@ -53,9 +63,9 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
applyTo(element, detectPropertyNameToUse(element)!!)
|
||||
}
|
||||
|
||||
public fun applyTo(element: JetCallExpression, propertyName: Name) {
|
||||
public fun applyTo(element: JetCallExpression, propertyName: Name): JetExpression {
|
||||
val arguments = element.getValueArguments()
|
||||
when (arguments.size()) {
|
||||
return when (arguments.size()) {
|
||||
0 -> replaceWithPropertyGet(element, propertyName)
|
||||
1 -> replaceWithPropertySet(element, propertyName, arguments.single())
|
||||
else -> error("More than one argument in call to accessor")
|
||||
@@ -75,8 +85,38 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
val resolutionScope = callExpression.getResolutionScope(bindingContext, callExpression.getResolutionFacade())
|
||||
val property = findSyntheticProperty(function, resolutionScope) ?: return null
|
||||
|
||||
val moduleDescriptor = callExpression.getResolutionFacade().findModuleDescriptor(callExpression)
|
||||
val dataFlowInfo = bindingContext.getDataFlowInfo(callee)
|
||||
val qualifiedExpression = callExpression.getQualifiedExpressionForSelectorOrThis()
|
||||
val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, qualifiedExpression] ?: TypeUtils.NO_EXPECTED_TYPE
|
||||
|
||||
if (!checkWillResolveToProperty(resolvedCall, property, bindingContext, resolutionScope, dataFlowInfo, expectedType, moduleDescriptor)) return null
|
||||
|
||||
val isSetUsage = callExpression.valueArguments.size() == 1
|
||||
if (isSetUsage && property.type != function.valueParameters.single().type) {
|
||||
val qualifiedExpressionCopy = qualifiedExpression.copied()
|
||||
val callExpressionCopy = ((qualifiedExpressionCopy as? JetQualifiedExpression)?.selectorExpression ?: qualifiedExpressionCopy) as JetCallExpression
|
||||
val newExpression = applyTo(callExpressionCopy, property.name)
|
||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace")
|
||||
val newBindingContext = newExpression.analyzeInContext(resolutionScope, bindingTrace, dataFlowInfo, expectedType, moduleDescriptor)
|
||||
if (newBindingContext.diagnostics.any { it.severity == Severity.ERROR && it.factory != Errors.ASSIGNMENT_IN_EXPRESSION_CONTEXT }) return null
|
||||
}
|
||||
|
||||
return property.name
|
||||
}
|
||||
|
||||
private fun checkWillResolveToProperty(
|
||||
resolvedCall: ResolvedCall<out CallableDescriptor>,
|
||||
property: SyntheticJavaPropertyDescriptor,
|
||||
bindingContext: BindingContext,
|
||||
resolutionScope: JetScope,
|
||||
dataFlowInfo: DataFlowInfo,
|
||||
expectedType: JetType,
|
||||
moduleDescriptor: ModuleDescriptor
|
||||
): Boolean {
|
||||
val project = resolvedCall.call.callElement.project
|
||||
val newCall = object : DelegatingCall(resolvedCall.call) {
|
||||
private val newCallee = JetPsiFactory(callElement).createExpressionByPattern("$0", property.name)
|
||||
private val newCallee = JetPsiFactory(project).createExpressionByPattern("$0", property.name)
|
||||
|
||||
override fun getCalleeExpression() = newCallee
|
||||
override fun getValueArgumentList(): JetValueArgumentList? = null
|
||||
@@ -84,20 +124,13 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
override fun getFunctionLiteralArguments(): List<FunctionLiteralArgument> = emptyList()
|
||||
}
|
||||
|
||||
val project = callExpression.project
|
||||
val moduleDescriptor = callExpression.getResolutionFacade().findModuleDescriptor(callExpression)
|
||||
val dataFlowInfo = bindingContext.getDataFlowInfo(callee)
|
||||
val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression.getQualifiedExpressionForSelectorOrThis()] ?: TypeUtils.NO_EXPECTED_TYPE
|
||||
|
||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "Temporary trace")
|
||||
val context = BasicCallResolutionContext.create(bindingTrace, resolutionScope, newCall, expectedType, dataFlowInfo,
|
||||
ContextDependency.INDEPENDENT, CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
|
||||
CallChecker.DoNothing, false)
|
||||
val callResolver = createContainerForMacros(project, moduleDescriptor).callResolver
|
||||
val result = callResolver.resolveSimpleProperty(context)
|
||||
if (!result.isSuccess || result.resultingDescriptor.original != property) return null
|
||||
|
||||
return property.name
|
||||
return result.isSuccess && result.resultingDescriptor.original == property
|
||||
}
|
||||
|
||||
private fun findSyntheticProperty(function: FunctionDescriptor, resolutionScope: JetScope): SyntheticJavaPropertyDescriptor? {
|
||||
@@ -110,13 +143,13 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
return null
|
||||
}
|
||||
|
||||
private fun replaceWithPropertyGet(callExpression: JetCallExpression, propertyName: Name) {
|
||||
private fun replaceWithPropertyGet(callExpression: JetCallExpression, propertyName: Name): JetExpression {
|
||||
val newExpression = JetPsiFactory(callExpression).createExpression(propertyName.render())
|
||||
callExpression.replace(newExpression)
|
||||
return callExpression.replaced(newExpression)
|
||||
}
|
||||
|
||||
//TODO: what if it was used as expression (of type Unit)?
|
||||
private fun replaceWithPropertySet(callExpression: JetCallExpression, propertyName: Name, argument: JetValueArgument) {
|
||||
private fun replaceWithPropertySet(callExpression: JetCallExpression, propertyName: Name, argument: JetValueArgument): JetExpression {
|
||||
val qualifiedExpression = callExpression.getQualifiedExpressionForSelector()
|
||||
if (qualifiedExpression != null) {
|
||||
val pattern = when (qualifiedExpression) {
|
||||
@@ -130,11 +163,11 @@ class UsePropertyAccessSyntaxIntention : JetSelfTargetingOffsetIndependentIntent
|
||||
propertyName,
|
||||
argument.getArgumentExpression()!!
|
||||
)
|
||||
qualifiedExpression.replace(newExpression)
|
||||
return qualifiedExpression.replaced(newExpression)
|
||||
}
|
||||
else {
|
||||
val newExpression = JetPsiFactory(callExpression).createExpressionByPattern("$0=$1", propertyName, argument.getArgumentExpression()!!)
|
||||
callExpression.replace(newExpression)
|
||||
return callExpression.replaced(newExpression)
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
interface JavaInterface {
|
||||
Object getSomething();
|
||||
void setSomething(Object value);
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// IS_APPLICABLE: false
|
||||
// WITH_RUNTIME
|
||||
|
||||
abstract class KotlinClass : JavaInterface {
|
||||
override fun getSomething(): String = ""
|
||||
}
|
||||
|
||||
fun foo(k: KotlinClass) {
|
||||
k.<caret>setSomething(1)
|
||||
}
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
interface JavaInterface {
|
||||
Object getSomething();
|
||||
void setSomething(Object value);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
interface JavaInterface {
|
||||
Object getSomething();
|
||||
void setSomething(Object value);
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
abstract class KotlinClass : JavaInterface {
|
||||
override fun getSomething(): String = ""
|
||||
}
|
||||
|
||||
fun foo(k: KotlinClass, p: Any) {
|
||||
if (p is String) {
|
||||
k.<caret>setSomething(p)
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
abstract class KotlinClass : JavaInterface {
|
||||
override fun getSomething(): String = ""
|
||||
}
|
||||
|
||||
fun foo(k: KotlinClass, p: Any) {
|
||||
if (p is String) {
|
||||
k.something = p
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7601,6 +7601,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyTypeIsMoreSpecific1.kt")
|
||||
public void testPropertyTypeIsMoreSpecific1() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyTypeIsMoreSpecific2.kt")
|
||||
public void testPropertyTypeIsMoreSpecific2() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("set.kt")
|
||||
public void testSet() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/set.kt");
|
||||
|
||||
Reference in New Issue
Block a user