diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt index b406d7d6647..2b44ac22772 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/synthetic/JavaSyntheticExtensionsScope.kt @@ -45,7 +45,9 @@ interface SyntheticJavaPropertyDescriptor : PropertyDescriptor { companion object { fun findByGetterOrSetter(getterOrSetter: FunctionDescriptor, resolutionScope: JetScope): SyntheticJavaPropertyDescriptor? { val name = getterOrSetter.getName() - if (propertyNameByGetMethodName(name) == null && propertyNameBySetMethodName(name) == null) return null // optimization + if (name.isSpecial()) return null + val identifier = name.getIdentifier() + if (!identifier.startsWith("get") && !identifier.startsWith("is") && !identifier.startsWith("set")) return null // optimization val owner = getterOrSetter.getContainingDeclaration() if (owner !is JavaClassDescriptor) return null @@ -56,15 +58,22 @@ interface SyntheticJavaPropertyDescriptor : PropertyDescriptor { } fun propertyNameByGetMethodName(methodName: Name): Name? - = propertyNameFromAccessorMethodName(methodName, "get") ?: propertyNameFromAccessorMethodName(methodName, "is") + = propertyNameFromAccessorMethodName(methodName, "get") ?: propertyNameFromAccessorMethodName(methodName, "is", removePrefix = false) - fun propertyNameBySetMethodName(methodName: Name): Name? - = propertyNameFromAccessorMethodName(methodName, "set") + fun propertyNameBySetMethodName(methodName: Name, withIsPrefix: Boolean): Name? + = propertyNameFromAccessorMethodName(methodName, "set", addPrefix = if (withIsPrefix) "is" else null) - private fun propertyNameFromAccessorMethodName(methodName: Name, prefix: String): Name? { + private fun propertyNameFromAccessorMethodName(methodName: Name, prefix: String, removePrefix: Boolean = true, addPrefix: String? = null): Name? { if (methodName.isSpecial()) return null val identifier = methodName.getIdentifier() if (!identifier.startsWith(prefix)) return null + + if (addPrefix != null) { + assert(removePrefix) + return Name.identifier(addPrefix + identifier.removePrefix(prefix)) + } + + if (!removePrefix) return methodName val name = Introspector.decapitalize(identifier.removePrefix(prefix)) if (!Name.isValidIdentifier(name)) return null return Name.identifier(name) @@ -103,7 +112,7 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by .singleOrNull { isGoodGetMethod(it) } ?: return null val propertyType = getMethod.getReturnType() ?: return null - val setMethod = memberScope.getFunctions(possibleSetMethodName(name)).singleOrNull { isGoodSetMethod(it, propertyType) } + val setMethod = memberScope.getFunctions(setMethodName(getMethod.getName())).singleOrNull { isGoodSetMethod(it, propertyType) } return MyPropertyDescriptor(javaClass, getMethod, setMethod, name, propertyType, type) } @@ -177,12 +186,24 @@ class JavaSyntheticExtensionsScope(storageManager: StorageManager) : JetScope by //TODO: reuse code with generation? private fun possibleGetMethodNames(propertyName: Name): Collection { - val capitalized = propertyName.getIdentifier().capitalize() - return listOf(Name.identifier("get" + capitalized), Name.identifier("is" + capitalized)) + val identifier = propertyName.getIdentifier() + val getPrefixName = Name.identifier("get" + identifier.capitalize()) + if (identifier.startsWith("is")) { + return listOf(propertyName, getPrefixName) + } + else { + return listOf(getPrefixName) + } } - private fun possibleSetMethodName(propertyName: Name): Name { - return Name.identifier("set" + propertyName.getIdentifier().capitalize()) + private fun setMethodName(getMethodName: Name): Name { + val identifier = getMethodName.getIdentifier() + val prefix = when { + identifier.startsWith("get") -> "get" + identifier.startsWith("is") -> "is" + else -> throw IllegalArgumentException() + } + return Name.identifier("set" + identifier.removePrefix(prefix).capitalize()) } private class MyPropertyDescriptor( diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.kt b/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.kt index c4883eddbf2..45eb42ecf0a 100644 --- a/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.kt +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.kt @@ -1,13 +1,30 @@ // FILE: KotlinFile.kt fun foo(javaClass: JavaClass) { - javaClass.something = !javaClass.something + javaClass.isSomething = !javaClass.isSomething + javaClass.isSomething2 = !javaClass.isSomething2 + javaClass.something + javaClass.isSomethingWrong javaClass.somethingWrong } // FILE: JavaClass.java public class JavaClass { - public boolean isSomething() { return true; } - public void setSomething(boolean value) { } - public int isSomethingWrong() { return 1; } + public boolean isSomething() { + return true; + } + + public void setSomething(boolean value) { + } + + public boolean getIsSomething2() { + return true; + } + + public void setIsSomething2(boolean value) { + } + + public int isSomethingWrong() { + return 1; + } } \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.txt b/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.txt index f80114acd07..93bde185b5b 100644 --- a/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.txt +++ b/compiler/testData/diagnostics/tests/syntheticExtensions/IsNaming.txt @@ -5,9 +5,11 @@ internal fun foo(/*0*/ javaClass: JavaClass): kotlin.Unit public open class JavaClass { public constructor JavaClass() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open fun getIsSomething2(): kotlin.Boolean public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public open fun isSomething(): kotlin.Boolean public open fun isSomethingWrong(): kotlin.Int + public open fun setIsSomething2(/*0*/ value: kotlin.Boolean): kotlin.Unit public open fun setSomething(/*0*/ value: kotlin.Boolean): kotlin.Unit public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt index d689db4a800..d0c873128ff 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/references/SyntheticPropertyAccessorReference.kt @@ -21,11 +21,12 @@ import com.intellij.psi.PsiElement import com.intellij.util.SmartList import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.JetNameReferenceExpression import org.jetbrains.kotlin.psi.JetPsiFactory -import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor import org.jetbrains.kotlin.utils.addIfNotNull @@ -56,10 +57,14 @@ sealed class SyntheticPropertyAccessorReference(expression: JetNameReferenceExpr if (!Name.isValidIdentifier(newElementName!!)) return expression val newNameAsName = Name.identifier(newElementName) - val newName = if (getter) + val newName = if (getter) { SyntheticJavaPropertyDescriptor.propertyNameByGetMethodName(newNameAsName) - else - SyntheticJavaPropertyDescriptor.propertyNameBySetMethodName(newNameAsName) + } + else { + val propertyDescriptor = super.getTargetDescriptors(expression.analyze(BodyResolveMode.PARTIAL)) + .singleOrNull { it is SyntheticJavaPropertyDescriptor } ?: return expression + SyntheticJavaPropertyDescriptor.propertyNameBySetMethodName(newNameAsName, withIsPrefix = propertyDescriptor.getName().asString().startsWith("is")) + } if (newName == null) return expression //TODO: handle the case when get/set becomes ordinary method val nameIdentifier = JetPsiFactory(expression).createNameIdentifier(newName.getIdentifier()) diff --git a/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt b/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt index cc74e6dfe9b..98c55ba5476 100644 --- a/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt +++ b/idea/idea-completion/testData/basic/common/extensions/SyntheticExtensions2.kt @@ -5,10 +5,10 @@ fun Thread.foo(urlConnection: java.net.URLConnection) { } // EXIST_JAVA_ONLY: { lookupString: "priority", itemText: "priority", tailText: " (from getPriority()/setPriority())", typeText: "Int" } -// EXIST_JAVA_ONLY: { lookupString: "daemon", itemText: "daemon", tailText: " (from isDaemon()/setDaemon())", typeText: "Boolean" } +// EXIST_JAVA_ONLY: { lookupString: "isDaemon", itemText: "isDaemon", tailText: " (from isDaemon()/setDaemon())", typeText: "Boolean" } // EXIST_JAVA_ONLY: { lookupString: "URL", itemText: "URL", tailText: " (from getURL())", typeText: "URL!" } // ABSENT: getPriority // ABSENT: setPriority -// ABSENT: isDaemon +// ABSENT: { itemText: "isDaemon", tailText: "()" } // ABSENT: setDaemon // ABSENT: getURL diff --git a/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt b/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt new file mode 100644 index 00000000000..4226c66661d --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(thread: Thread) { + thread.isDaemon() +} \ No newline at end of file diff --git a/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt.after new file mode 100644 index 00000000000..2d219caa3cd --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/isGet.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(thread: Thread) { + thread.isDaemon +} \ No newline at end of file diff --git a/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt b/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt new file mode 100644 index 00000000000..d85a6bc74a5 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(thread: Thread) { + thread.setDaemon(true) +} \ No newline at end of file diff --git a/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt.after b/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt.after new file mode 100644 index 00000000000..51f85fe5da9 --- /dev/null +++ b/idea/testData/intentions/usePropertyAccessSyntax/isSet.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo(thread: Thread) { + thread.isDaemon = true +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/Usages.kt b/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/Usages.kt new file mode 100644 index 00000000000..2d1769e76d3 --- /dev/null +++ b/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/Usages.kt @@ -0,0 +1,5 @@ +import testing.JavaClass + +fun usages(javaClass: JavaClass) { + javaClass.isSomethingNew = !javaClass.isSomething +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/testing/JavaClass.java b/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/testing/JavaClass.java new file mode 100644 index 00000000000..c1d68acf1af --- /dev/null +++ b/idea/testData/refactoring/rename/syntheticPropertyUsages3/after/testing/JavaClass.java @@ -0,0 +1,6 @@ +package testing; + +public class JavaClass { + public boolean isSomething() { return true; } + public void setSomethingNew(boolean value) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/Usages.kt b/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/Usages.kt new file mode 100644 index 00000000000..686f44b4a3e --- /dev/null +++ b/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/Usages.kt @@ -0,0 +1,5 @@ +import testing.JavaClass + +fun usages(javaClass: JavaClass) { + javaClass.isSomething = !javaClass.isSomething +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/testing/JavaClass.java b/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/testing/JavaClass.java new file mode 100644 index 00000000000..330068bb7d8 --- /dev/null +++ b/idea/testData/refactoring/rename/syntheticPropertyUsages3/before/testing/JavaClass.java @@ -0,0 +1,6 @@ +package testing; + +public class JavaClass { + public boolean isSomething() { return true; } + public void setSomething(boolean value) {} +} \ No newline at end of file diff --git a/idea/testData/refactoring/rename/syntheticPropertyUsages3/renameSetMethod.test b/idea/testData/refactoring/rename/syntheticPropertyUsages3/renameSetMethod.test new file mode 100644 index 00000000000..cfbc1f7d38a --- /dev/null +++ b/idea/testData/refactoring/rename/syntheticPropertyUsages3/renameSetMethod.test @@ -0,0 +1,6 @@ +{ + "type": "JAVA_METHOD", + "classId": "testing/JavaClass", + "methodSignature": "void setSomething(boolean value)", + "newName": "setSomethingNew" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 67130b4b574..05e05dc3277 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -7433,6 +7433,18 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("isGet.kt") + public void testIsGet() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/isGet.kt"); + doTest(fileName); + } + + @TestMetadata("isSet.kt") + public void testIsSet() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/isSet.kt"); + doTest(fileName); + } + @TestMetadata("set.kt") public void testSet() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/intentions/usePropertyAccessSyntax/set.kt"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java index 22b18c54325..05d62c0d780 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/rename/RenameTestGenerated.java @@ -382,4 +382,10 @@ public class RenameTestGenerated extends AbstractRenameTest { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/syntheticPropertyUsages2/renameSetMethod.test"); doTest(fileName); } + + @TestMetadata("syntheticPropertyUsages3/renameSetMethod.test") + public void testSyntheticPropertyUsages3_RenameSetMethod() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/rename/syntheticPropertyUsages3/renameSetMethod.test"); + doTest(fileName); + } }