Added fallback for LightClasses getOwnFields, getOwnMethods if dummy resolve failed (KT-27243, KT-33561)

This commit is contained in:
Vladimir Dolzhenko
2019-08-29 15:46:57 +02:00
parent 78b0d5750b
commit a2fcefd456
4 changed files with 134 additions and 27 deletions
@@ -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 }
@@ -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)
}
}
}