From a2fcefd45676848f2ce3fac517f18cdad0912c45 Mon Sep 17 00:00:00 2001 From: Vladimir Dolzhenko Date: Thu, 29 Aug 2019 15:46:57 +0200 Subject: [PATCH] Added fallback for LightClasses getOwnFields, getOwnMethods if dummy resolve failed (KT-27243, KT-33561) --- .../lightClasses/IDELightClassContexts.kt | 13 ++- .../lightClasses/LazyLightClassDataHolder.kt | 58 +++++++----- .../lightClasses/LightClassEqualsTest.java | 2 +- .../idea/lightClasses/LightClassSampleTest.kt | 88 +++++++++++++++++++ 4 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassSampleTest.kt diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/IDELightClassContexts.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/IDELightClassContexts.kt index 684993bf0d0..a6b9376a894 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/IDELightClassContexts.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/IDELightClassContexts.kt @@ -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().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().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 } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt index ad37bbbb26b..ca991d14fbb 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/lightClasses/LazyLightClassDataHolder.kt @@ -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?.assertMatches(dummyMember: T, containingClass: KtLightClass): T { - if (this == null) - throw LazyLightClassMemberMatchingError.NoMatch(dummyMember, containingClass) + private fun 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 diff --git a/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassEqualsTest.java b/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassEqualsTest.java index f9b682650aa..aee8d9f7915 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassEqualsTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassEqualsTest.java @@ -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); diff --git a/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassSampleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassSampleTest.kt new file mode 100644 index 00000000000..37d87f63141 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/lightClasses/LightClassSampleTest.kt @@ -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) + } + } + +} \ No newline at end of file