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.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.util.CallMaker import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator 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.jvm.platform.JvmPlatformAnalyzerServices
import org.jetbrains.kotlin.resolve.lazy.FileScopeProviderImpl import org.jetbrains.kotlin.resolve.lazy.FileScopeProviderImpl
import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil import org.jetbrains.kotlin.resolve.lazy.ForceResolveUtil
@@ -81,9 +82,9 @@ import org.jetbrains.kotlin.utils.sure
class IDELightClassConstructionContext( class IDELightClassConstructionContext(
bindingContext: BindingContext, module: ModuleDescriptor, bindingContext: BindingContext, module: ModuleDescriptor,
langaugeVersionSettings: LanguageVersionSettings, languageVersionSettings: LanguageVersionSettings,
val mode: Mode val mode: Mode
) : LightClassConstructionContext(bindingContext, module, langaugeVersionSettings) { ) : LightClassConstructionContext(bindingContext, module, languageVersionSettings) {
enum class Mode { enum class Mode {
LIGHT, LIGHT,
EXACT EXACT
@@ -193,6 +194,8 @@ internal object IDELightClassContexts {
if (hasSerializationLikeAnnotations(classOrObject)) return false if (hasSerializationLikeAnnotations(classOrObject)) return false
if (hasJvmSyntheticMembers(classOrObject)) return false
return classOrObject.declarations.filterIsInstance<KtClassOrObject>().all { isDummyResolveApplicable(it) } return classOrObject.declarations.filterIsInstance<KtClassOrObject>().all { isDummyResolveApplicable(it) }
} }
@@ -215,6 +218,12 @@ internal object IDELightClassContexts {
private fun isSerializableOrSerializerFqName(fqName: FqName?) = private fun isSerializableOrSerializerFqName(fqName: FqName?) =
fqName == KOTLINX_SERIALIZABLE_FQ_NAME || fqName == KOTLINX_SERIALIZER_FQ_NAME 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) = private fun hasDelegatedSupertypes(classOrObject: KtClassOrObject) =
classOrObject.superTypeListEntries.any { it is KtDelegatedSuperTypeEntry } classOrObject.superTypeListEntries.any { it is KtDelegatedSuperTypeEntry }
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.caches.lightClasses package org.jetbrains.kotlin.idea.caches.lightClasses
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMember 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.psi.debugText.getDebugText
import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics
import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
typealias ExactLightClassContextProvider = () -> LightClassConstructionContext typealias ExactLightClassContextProvider = () -> LightClassConstructionContext
typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)? typealias DummyLightClassContextProvider = (() -> LightClassConstructionContext?)?
@@ -112,7 +112,14 @@ sealed class LazyLightClassDataHolder(
val fieldName = dummyField.name val fieldName = dummyField.name
KtLightFieldImpl.lazy(dummyField, fieldOrigin, containingClass) { 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 } val byMemberIndex: (PsiMethod) -> Boolean = { it.memberIndex == dummyIndex }
/* Searching all methods may be necessary in some cases where we failed to rollback optimization: /* 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 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) val findMethodsByName = clsDelegate.findMethodsByName(dummyMethod.name, false)
?: clsDelegate.methods.firstOrNull(byMemberIndex)
exactDelegateMethod.assertMatches(dummyMethod, containingClass) 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 { private fun <T : PsiMember> T?.checkMatches(dummyMember: T, containingClass: KtLightClass): T? {
if (this == null) if (this == null) {
throw LazyLightClassMemberMatchingError.NoMatch(dummyMember, containingClass) logMismatch("Couldn't match ${dummyMember.debugName}", containingClass)
return null
}
val parameterCountMatches = (this as? PsiMethod)?.parameterList?.parametersCount ?: 0 == val parameterCountMatches = (this as? PsiMethod)?.parameterList?.parametersCount ?: 0 ==
(dummyMember as? PsiMethod)?.parameterList?.parametersCount ?: 0 (dummyMember as? PsiMethod)?.parameterList?.parametersCount ?: 0
if (this.memberIndex != dummyMember.memberIndex || !parameterCountMatches) { 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 return this
} }
}
private sealed class LazyLightClassMemberMatchingError(message: String, containingClass: KtLightClass) : companion object {
KotlinExceptionWithAttachments(message) { private val LOG = Logger.getInstance(LazyLightClassDataHolder::class.java)
init { private fun logMismatch(message: String, containingClass: KtLightClass) {
containingClass.kotlinOrigin?.hasLightClassMatchingErrors = true containingClass.kotlinOrigin?.hasLightClassMatchingErrors = true
withAttachment("class.kt", (containingClass.kotlinOrigin)?.getDebugText()) 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 private val PsiMember.debugName
@@ -46,7 +46,7 @@ public class LightClassEqualsTest extends KotlinLightCodeInsightFixtureTestCase
doTestEquals(((KtLightClass) theClass).getKotlinOrigin()); doTestEquals(((KtLightClass) theClass).getKotlinOrigin());
} }
private static void doTestEquals(@Nullable KtClassOrObject origin) { static void doTestEquals(@Nullable KtClassOrObject origin) {
assertNotNull(origin); assertNotNull(origin);
PsiClass lightClass1 = KtLightClassForSourceDeclaration.Companion.createNoCache(origin, true); 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)
}
}
}