UsePropertyAccessSyntaxIntention: handled case when property type is more specific

This commit is contained in:
Valentin Kipyatkov
2015-08-12 17:57:08 +03:00
parent 6fcb75cfb5
commit 7b149c10c1
8 changed files with 108 additions and 16 deletions
@@ -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)
}
}
}
@@ -0,0 +1,4 @@
interface JavaInterface {
Object getSomething();
void setSomething(Object value);
}
@@ -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)
}
@@ -0,0 +1,4 @@
interface JavaInterface {
Object getSomething();
void setSomething(Object value);
}
@@ -0,0 +1,4 @@
interface JavaInterface {
Object getSomething();
void setSomething(Object value);
}
@@ -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)
}
}
@@ -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");