diff --git a/ChangeLog.md b/ChangeLog.md index eef7eabedf5..44c41c87c26 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -199,6 +199,7 @@ - [`KT-11933`](https://youtrack.jetbrains.com/issue/KT-11933) Entities used only by alias are marked as unused - [`KT-12193`](https://youtrack.jetbrains.com/issue/KT-12193) Convert to block body isn't equivalent for when expressions returning Unit - [`KT-10779`](https://youtrack.jetbrains.com/issue/KT-10779) Simplify 'for' using destructing declaration: intention / inspection quick fix is available only when all variables are used +- [`KT-11281`](https://youtrack.jetbrains.com/issue/KT-11281) Fix exception on applying "Convert to class" intention to Java interface with Kotlin inheritor(s) - [`KT-12285`](https://youtrack.jetbrains.com/issue/KT-12285) Fix exception on test class generation - [`KT-12502`](https://youtrack.jetbrains.com/issue/KT-12502) Convert to expression body should be forbidden for non-exhaustive when returning Unit - [`KT-12260`](https://youtrack.jetbrains.com/issue/KT-12260) ISE while replacing an operator with safe call diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtSuperTypeList.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtSuperTypeList.java index 3165d39adb6..c3ff7c2cec0 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtSuperTypeList.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtSuperTypeList.java @@ -44,6 +44,11 @@ public class KtSuperTypeList extends KtElementImplStub> { private val lightIdentifier = KtLightIdentifier(this, classOrObject) + private val _extendsList by lazy(LazyThreadSafetyMode.PUBLICATION) { + val listDelegate = super.getExtendsList() ?: return@lazy null + KtLightPsiReferenceList(listDelegate, this) + } + + private val _implementsList by lazy(LazyThreadSafetyMode.PUBLICATION) { + val listDelegate = super.getImplementsList() ?: return@lazy null + KtLightPsiReferenceList(listDelegate, this) + } + private fun getLocalClassParent(): PsiElement? { fun getParentByPsiMethod(method: PsiMethod?, name: String?, forceMethodWrapping: Boolean): PsiElement? { if (method == null || name == null) return null @@ -396,6 +406,10 @@ open class KtLightClassForExplicitDeclaration( override fun getNameIdentifier(): KtLightIdentifier? = lightIdentifier + override fun getExtendsList() = _extendsList + + override fun getImplementsList() = _implementsList + companion object { private val JAVA_API_STUB = Key.create>("JAVA_API_STUB") diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightPsiReferenceList.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightPsiReferenceList.kt new file mode 100644 index 00000000000..79ab8a6966c --- /dev/null +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightPsiReferenceList.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.asJava + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiJavaCodeReferenceElement +import com.intellij.psi.PsiReferenceList +import com.intellij.psi.PsiReferenceList.Role +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtSuperTypeList +import org.jetbrains.kotlin.psi.KtSuperTypeListEntry +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe + +class KtLightPsiReferenceList ( + override val clsDelegate: PsiReferenceList, + private val owner: KtLightClass +) : KtLightElement, PsiReferenceList by clsDelegate { + inner class KtLightSuperTypeReference( + override val clsDelegate: PsiJavaCodeReferenceElement + ) : KtLightElement, PsiJavaCodeReferenceElement by clsDelegate { + override fun getName() = null + override fun setName(name: String) = throw UnsupportedOperationException() + + override val kotlinOrigin by lazy(LazyThreadSafetyMode.PUBLICATION) { + val superTypeList = this@KtLightPsiReferenceList.kotlinOrigin ?: return@lazy null + val fqNameToFind = clsDelegate.qualifiedName ?: return@lazy null + val context = LightClassGenerationSupport.getInstance(project).analyze(superTypeList) + superTypeList.entries.firstOrNull { + val referencedType = context[BindingContext.TYPE, it.typeReference] + referencedType?.constructor?.declarationDescriptor?.fqNameUnsafe?.asString() == fqNameToFind + } + } + + override fun getParent() = this@KtLightPsiReferenceList + + override fun delete() { + val superTypeList = this@KtLightPsiReferenceList.kotlinOrigin ?: return + val entry = kotlinOrigin ?: return + superTypeList.removeEntry(entry) + } + } + + override val kotlinOrigin: KtSuperTypeList? + get() = owner.kotlinOrigin?.getSuperTypeList() + + private val _referenceElements by lazy(LazyThreadSafetyMode.PUBLICATION) { + clsDelegate.referenceElements.map { KtLightSuperTypeReference(it) }.toTypedArray() + } + + override fun getName() = null + override fun setName(name: String) = throw UnsupportedOperationException() + + override fun getParent() = owner + + override fun getReferenceElements() = _referenceElements + + override fun add(element: PsiElement): PsiElement? { + if (element !is KtLightSuperTypeReference) throw UnsupportedOperationException("Unexpected element: ${element.getElementTextWithContext()}") + + val superTypeList = kotlinOrigin ?: return element + val entry = element.kotlinOrigin ?: return element + // Only classes may be mentioned in 'extends' list, thus create super call instead simple type reference + val entryToAdd = if ((element.parent as? PsiReferenceList)?.role == Role.IMPLEMENTS_LIST && role == Role.EXTENDS_LIST) { + KtPsiFactory(this).createSuperTypeCallEntry("${entry.text}()") + } + else entry + // TODO: implement KtSuperListEntry qualification/shortening when inserting reference from another context + if (entry.parent != superTypeList) { + superTypeList.addEntry(entryToAdd) + } + else { + // Preserve original entry order + entry.replace(entryToAdd) + } + return element + } +} diff --git a/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.Dependency.kt b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.Dependency.kt new file mode 100644 index 00000000000..2b91ddccfcf --- /dev/null +++ b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.Dependency.kt @@ -0,0 +1,9 @@ +interface IK + +class C2 : I() { + +} + +class C3: I(), IK { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.java b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.java new file mode 100644 index 00000000000..1d2fdaf270a --- /dev/null +++ b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.after.java @@ -0,0 +1,4 @@ +// "Convert to 'class'" "true" +public abstract class I { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Dependency.kt b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Dependency.kt new file mode 100644 index 00000000000..61b8b89b4d3 --- /dev/null +++ b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Dependency.kt @@ -0,0 +1,9 @@ +interface IK + +class C2 : I { + +} + +class C3: I, IK { + +} \ No newline at end of file diff --git a/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Main.java b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Main.java new file mode 100644 index 00000000000..1da4d4b7019 --- /dev/null +++ b/idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Main.java @@ -0,0 +1,4 @@ +// "Convert to 'class'" "true" +public interface I { + +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java index 1cb6b2e2641..fe89a6c6d2d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiFileTestGenerated.java @@ -544,6 +544,21 @@ public class QuickFixMultiFileTestGenerated extends AbstractQuickFixMultiFileTes } + @TestMetadata("idea/testData/quickfix/convertJavaInterfaceToClass") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertJavaInterfaceToClass extends AbstractQuickFixMultiFileTest { + public void testAllFilesPresentInConvertJavaInterfaceToClass() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/convertJavaInterfaceToClass"), Pattern.compile("^(\\w+)\\.((before\\.Main\\.\\w+)|(test))$"), true); + } + + @TestMetadata("kotlinInheritor.before.Main.java") + public void testKotlinInheritor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/convertJavaInterfaceToClass/kotlinInheritor.before.Main.java"); + doTestWithExtraFile(fileName); + } + } + @TestMetadata("idea/testData/quickfix/createFromUsage") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)