From 7b149c10c1aafa262288441c5fd4d6d99a7c37e5 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 12 Aug 2015 17:57:08 +0300 Subject: [PATCH] UsePropertyAccessSyntaxIntention: handled case when property type is more specific --- .../UsePropertyAccessSyntaxIntention.kt | 65 ++++++++++++++----- .../propertyTypeIsMoreSpecific1.1.java | 4 ++ .../propertyTypeIsMoreSpecific1.kt | 11 ++++ .../propertyTypeIsMoreSpecific2.1.java | 4 ++ .../propertyTypeIsMoreSpecific2.1.java.after | 4 ++ .../propertyTypeIsMoreSpecific2.kt | 12 ++++ .../propertyTypeIsMoreSpecific2.kt.after | 12 ++++ .../intentions/IntentionTestGenerated.java | 12 ++++ 8 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.1.java create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.kt create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java.after create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt create mode 100644 idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt.after diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/UsePropertyAccessSyntaxIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/UsePropertyAccessSyntaxIntention.kt index 1dcc2534942..55ebd415223 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/UsePropertyAccessSyntaxIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/UsePropertyAccessSyntaxIntention.kt @@ -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(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, + 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 = 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) } } } \ No newline at end of file diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.1.java b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.1.java new file mode 100644 index 00000000000..378f21ce025 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.1.java @@ -0,0 +1,4 @@ +interface JavaInterface { + Object getSomething(); + void setSomething(Object value); +} diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.kt b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.kt new file mode 100644 index 00000000000..2dd7b520d19 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific1.kt @@ -0,0 +1,11 @@ +// IS_APPLICABLE: false +// WITH_RUNTIME + +abstract class KotlinClass : JavaInterface { + override fun getSomething(): String = "" +} + +fun foo(k: KotlinClass) { + k.setSomething(1) +} + diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java new file mode 100644 index 00000000000..378f21ce025 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java @@ -0,0 +1,4 @@ +interface JavaInterface { + Object getSomething(); + void setSomething(Object value); +} diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java.after b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java.after new file mode 100644 index 00000000000..378f21ce025 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.1.java.after @@ -0,0 +1,4 @@ +interface JavaInterface { + Object getSomething(); + void setSomething(Object value); +} diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt new file mode 100644 index 00000000000..55db609029a --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt @@ -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.setSomething(p) + } +} + diff --git a/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt.after new file mode 100644 index 00000000000..c629238a34f --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/propertyTypeIsMoreSpecific2.kt.after @@ -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 + } +} + diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 1eb01c4f330..faa354711af 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -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");