Added fallback for LightClasses getOwnFields, getOwnMethods if dummy resolve failed (KT-27243, KT-33561)
This commit is contained in:
+11
-2
@@ -64,6 +64,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
|
||||
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
|
||||
import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
|
||||
import org.jetbrains.kotlin.resolve.lazy.FileScopeProviderImpl
|
||||
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
|
||||
@@ -81,9 +82,9 @@ import org.jetbrains.kotlin.utils.sure
|
||||
|
||||
class IDELightClassConstructionContext(
|
||||
bindingContext: BindingContext, module: ModuleDescriptor,
|
||||
langaugeVersionSettings: LanguageVersionSettings,
|
||||
languageVersionSettings: LanguageVersionSettings,
|
||||
val mode: Mode
|
||||
) : LightClassConstructionContext(bindingContext, module, langaugeVersionSettings) {
|
||||
) : LightClassConstructionContext(bindingContext, module, languageVersionSettings) {
|
||||
enum class Mode {
|
||||
LIGHT,
|
||||
EXACT
|
||||
@@ -193,6 +194,8 @@ internal object IDELightClassContexts {
|
||||
|
||||
if (hasSerializationLikeAnnotations(classOrObject)) return false
|
||||
|
||||
if (hasJvmSyntheticMembers(classOrObject)) return false
|
||||
|
||||
return classOrObject.declarations.filterIsInstance<KtClassOrObject>().all { isDummyResolveApplicable(it) }
|
||||
}
|
||||
|
||||
@@ -215,6 +218,12 @@ internal object IDELightClassContexts {
|
||||
private fun isSerializableOrSerializerFqName(fqName: FqName?) =
|
||||
fqName == KOTLINX_SERIALIZABLE_FQ_NAME || fqName == KOTLINX_SERIALIZER_FQ_NAME
|
||||
|
||||
private fun hasJvmSyntheticMembers(classOrObject: KtClassOrObject) =
|
||||
classOrObject.declarations.filterIsInstance<KtFunction>().any { isJvmSynthetic(it) }
|
||||
|
||||
private fun isJvmSynthetic(fn: KtFunction) =
|
||||
fn.annotationEntries.any { it.shortName == JVM_SYNTHETIC_ANNOTATION_FQ_NAME.shortName() }
|
||||
|
||||
private fun hasDelegatedSupertypes(classOrObject: KtClassOrObject) =
|
||||
classOrObject.superTypeListEntries.any { it is KtDelegatedSuperTypeEntry }
|
||||
|
||||
|
||||
+34
-24
@@ -5,6 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.lightClasses
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiMember
|
||||
@@ -23,7 +24,6 @@ import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
|
||||
import org.jetbrains.kotlin.psi.debugText.getDebugText
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
|
||||
import org.jetbrains.kotlin.storage.StorageManager
|
||||
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
|
||||
|
||||
typealias ExactLightClassContextProvider = () -> LightClassConstructionContext
|
||||
typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)?
|
||||
@@ -112,7 +112,14 @@ sealed class LazyLightClassDataHolder(
|
||||
|
||||
val fieldName = dummyField.name
|
||||
KtLightFieldImpl.lazy(dummyField, fieldOrigin, containingClass) {
|
||||
clsDelegate.findFieldByName(fieldName, false).assertMatches(dummyField, containingClass)
|
||||
val findFieldByName = clsDelegate.findFieldByName(fieldName, false)
|
||||
findFieldByName.checkMatches(dummyField, containingClass) ?:
|
||||
// fallback in case of non-matched (like synthetic) fields
|
||||
//
|
||||
// it costs some performance and has to happen in rare and odd cases
|
||||
KtLightFieldImpl.create(
|
||||
KtLightFieldImpl.getOrigin(dummyField), dummyField, containingClass
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,47 +136,50 @@ sealed class LazyLightClassDataHolder(
|
||||
val byMemberIndex: (PsiMethod) -> Boolean = { it.memberIndex == dummyIndex }
|
||||
|
||||
/* Searching all methods may be necessary in some cases where we failed to rollback optimization:
|
||||
Overriding internal member that was final
|
||||
Overriding internal member that was final
|
||||
|
||||
Resulting light member is not consistent in this case, so this should happen only for erroneous code
|
||||
*/
|
||||
val exactDelegateMethod = clsDelegate.findMethodsByName(dummyMethod.name, false).firstOrNull(byMemberIndex)
|
||||
?: clsDelegate.methods.firstOrNull(byMemberIndex)
|
||||
exactDelegateMethod.assertMatches(dummyMethod, containingClass)
|
||||
val findMethodsByName = clsDelegate.findMethodsByName(dummyMethod.name, false)
|
||||
|
||||
val candidateDelegateMethod = findMethodsByName.firstOrNull(byMemberIndex)
|
||||
?: clsDelegate.methods.firstOrNull(byMemberIndex)
|
||||
|
||||
candidateDelegateMethod.checkMatches(dummyMethod, containingClass) ?:
|
||||
// fallback if unable to find method for a dummy method (e.g. synthetic methods marked explicit or implicit) are
|
||||
// not visible as own methods.
|
||||
//
|
||||
// it costs some performance and has to happen in rare and odd cases
|
||||
KtLightMethodImpl.create(dummyMethod, KtLightMethodImpl.getOrigin(dummyMethod), containingClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : PsiMember> T?.assertMatches(dummyMember: T, containingClass: KtLightClass): T {
|
||||
if (this == null)
|
||||
throw LazyLightClassMemberMatchingError.NoMatch(dummyMember, containingClass)
|
||||
private fun <T : PsiMember> T?.checkMatches(dummyMember: T, containingClass: KtLightClass): T? {
|
||||
if (this == null) {
|
||||
logMismatch("Couldn't match ${dummyMember.debugName}", containingClass)
|
||||
return null
|
||||
}
|
||||
|
||||
val parameterCountMatches = (this as? PsiMethod)?.parameterList?.parametersCount ?: 0 ==
|
||||
(dummyMember as? PsiMethod)?.parameterList?.parametersCount ?: 0
|
||||
if (this.memberIndex != dummyMember.memberIndex || !parameterCountMatches) {
|
||||
throw LazyLightClassMemberMatchingError.WrongMatch(this, dummyMember, containingClass)
|
||||
logMismatch("Wrongly matched ${dummyMember.debugName} to ${this.debugName}", containingClass)
|
||||
return null
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LazyLightClassMemberMatchingError(message: String, containingClass: KtLightClass) :
|
||||
KotlinExceptionWithAttachments(message) {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(LazyLightClassDataHolder::class.java)
|
||||
|
||||
init {
|
||||
containingClass.kotlinOrigin?.hasLightClassMatchingErrors = true
|
||||
withAttachment("class.kt", (containingClass.kotlinOrigin)?.getDebugText())
|
||||
private fun logMismatch(message: String, containingClass: KtLightClass) {
|
||||
containingClass.kotlinOrigin?.hasLightClassMatchingErrors = true
|
||||
LOG.warn("$message, class.kt: ${(containingClass.kotlinOrigin)?.getDebugText()}")
|
||||
}
|
||||
}
|
||||
|
||||
class NoMatch(dummyMember: PsiMember, containingClass: KtLightClass) : LazyLightClassMemberMatchingError(
|
||||
"Couldn't match ${dummyMember.debugName}", containingClass
|
||||
)
|
||||
|
||||
class WrongMatch(realMember: PsiMember, dummyMember: PsiMember, containingClass: KtLightClass) : LazyLightClassMemberMatchingError(
|
||||
"Matched ${dummyMember.debugName} to ${realMember.debugName}", containingClass
|
||||
)
|
||||
}
|
||||
|
||||
private val PsiMember.debugName
|
||||
|
||||
@@ -46,7 +46,7 @@ public class LightClassEqualsTest extends KotlinLightCodeInsightFixtureTestCase
|
||||
doTestEquals(((KtLightClass) theClass).getKotlinOrigin());
|
||||
}
|
||||
|
||||
private static void doTestEquals(@Nullable KtClassOrObject origin) {
|
||||
static void doTestEquals(@Nullable KtClassOrObject origin) {
|
||||
assertNotNull(origin);
|
||||
|
||||
PsiClass lightClass1 = KtLightClassForSourceDeclaration.Companion.createNoCache(origin, true);
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.lightClasses
|
||||
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.testFramework.LightProjectDescriptor
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.idea.lightClasses.LightClassEqualsTest.doTestEquals
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.test.JUnit3WithIdeaConfigurationRunner
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(JUnit3WithIdeaConfigurationRunner::class)
|
||||
class LightClassSampleTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
}
|
||||
|
||||
fun testDeprecationLevelHIDDEN() {
|
||||
// KT27243
|
||||
myFixture.configureByText(
|
||||
"a.kt", """
|
||||
interface A {
|
||||
@Deprecated(level = DeprecationLevel.HIDDEN, message = "nothing")
|
||||
fun foo()
|
||||
} """.trimIndent()
|
||||
)
|
||||
|
||||
myFixture.configureByText(
|
||||
"b.kt", """
|
||||
class B : A {
|
||||
override fun foo() {}
|
||||
}""".trimIndent()
|
||||
)
|
||||
|
||||
doTestAndCheck("B", "foo", 1)
|
||||
}
|
||||
|
||||
fun testJvmSynthetic() {
|
||||
// KT33561
|
||||
myFixture.configureByText(
|
||||
"foo.kt", """
|
||||
class Foo {
|
||||
@JvmSynthetic
|
||||
inline fun foo(crossinline getter: () -> String) = foo(getter())
|
||||
|
||||
fun foo(getter: String) = println(getter)
|
||||
} """.trimIndent()
|
||||
)
|
||||
|
||||
doTestAndCheck("Foo", "foo", 1)
|
||||
}
|
||||
|
||||
private fun doTestAndCheck(className: String, methodName: String, methods: Int) {
|
||||
withLightClasses {
|
||||
val theClass: PsiClass = myFixture.javaFacade.findClass(className)
|
||||
assertNotNull(theClass)
|
||||
UsefulTestCase.assertInstanceOf(
|
||||
theClass,
|
||||
KtLightClassForSourceDeclaration::class.java
|
||||
)
|
||||
doTestEquals((theClass as KtLightClass).kotlinOrigin)
|
||||
assertEquals(methods, (theClass.allMethods + theClass.allMethods.flatMap { it.findSuperMethods().toList() })
|
||||
.count { it.name == methodName })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun withLightClasses(block: () -> Any) {
|
||||
val registryValue = Registry.get("kotlin.use.ultra.light.classes")
|
||||
val initialValue = registryValue.asBoolean()
|
||||
registryValue.setValue(false)
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
registryValue.setValue(initialValue)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user