Commit 14536e2b authored by Jinseong Jeon's avatar Jinseong Jeon
Browse files

FIR/UAST: introduce fake nullability annotation for fake method

@Deprecated or synthetic methods won't be generated by LC.
For source analysis purpose, though, UAST adds "fake" light method.
While doing so, it needs to mimic the addition of nullability annotation
by LC. Otherwise, e.g., if one method becomes @Deprecated, its return
type nullability could be stripped too, which can be seen as a breaking
API change (for some tool, such as Metalava).

^KTIJ-23807 Fixed
parent ca687354
Showing with 74 additions and 13 deletions
+74 -13
......@@ -9,12 +9,15 @@ import com.intellij.psi.impl.light.LightModifierList
import com.intellij.psi.impl.light.LightParameterListBuilder
import com.intellij.psi.impl.light.LightReferenceListBuilder
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.kotlin.asJava.elements.KotlinLightTypeParameterBuilder
import org.jetbrains.kotlin.asJava.elements.KotlinLightTypeParameterListBuilder
import org.jetbrains.kotlin.asJava.elements.KtLightAnnotationForSourceEntry
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
import org.jetbrains.kotlin.analysis.api.types.KtTypeNullability
import org.jetbrains.kotlin.asJava.classes.cannotModify
import org.jetbrains.kotlin.asJava.elements.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.uast.UastErrorType
import org.jetbrains.uast.kotlin.BaseKotlinUastResolveProviderService
......@@ -149,14 +152,27 @@ abstract class UastFakeLightMethodBase<T: KtDeclaration>(
}
private val _annotations: Array<PsiAnnotation> by lz {
original.annotationEntries.map { entry ->
val annotations = SmartList<PsiAnnotation>()
val isUnitFunction = original is KtFunction && _returnType == PsiType.VOID
// Do not annotate Unit function
if (!isUnitFunction) {
val nullability = baseResolveProviderService.nullability(original)
if (nullability != null && nullability != KtTypeNullability.UNKNOWN) {
annotations.add(
UastFakeLightNullabilityAnnotation(nullability, this)
)
}
}
original.annotationEntries.mapTo(annotations) { entry ->
KtLightAnnotationForSourceEntry(
name = entry.shortName?.identifier,
lazyQualifiedName = { baseResolveProviderService.qualifiedAnnotationName(entry) },
kotlinOrigin = entry,
parent = original,
)
}.toTypedArray()
}
annotations.toTypedArray()
}
override fun getAnnotations(): Array<PsiAnnotation> {
......@@ -177,8 +193,12 @@ abstract class UastFakeLightMethodBase<T: KtDeclaration>(
return original is KtConstructor<*>
}
private val _returnType: PsiType? by lz {
baseResolveProviderService.getType(original, this)
}
override fun getReturnType(): PsiType? {
return baseResolveProviderService.getType(original, this)
return _returnType
}
override fun getParent(): PsiElement? = containingClass
......@@ -196,3 +216,31 @@ abstract class UastFakeLightMethodBase<T: KtDeclaration>(
override fun hashCode(): Int = original.hashCode()
}
private class UastFakeLightNullabilityAnnotation(
private val nullability: KtTypeNullability,
parent: PsiElement
) : KtLightAbstractAnnotation(parent) {
override val kotlinOrigin: KtCallElement?
get() = null
override fun findAttributeValue(attributeName: String?): PsiAnnotationMemberValue? = null
override fun findDeclaredAttributeValue(attributeName: String?): PsiAnnotationMemberValue? = null
override fun getNameReferenceElement(): PsiJavaCodeReferenceElement? = null
override fun getParameterList(): PsiAnnotationParameterList = KtLightEmptyAnnotationParameterList(this)
override fun getQualifiedName(): String? =
when (nullability) {
KtTypeNullability.NON_NULLABLE -> NotNull::class.qualifiedName
KtTypeNullability.NULLABLE -> Nullable::class.qualifiedName
KtTypeNullability.UNKNOWN -> null
}
override fun toString() = "@$qualifiedName"
override fun <T : PsiAnnotationMemberValue?> setDeclaredAttributeValue(attributeName: String?, value: T?): T = cannotModify()
}
\ No newline at end of file
......@@ -52,11 +52,12 @@ interface UastApiFixtureTestBase : UastPluginSelection {
fun checkDetailsOfDeprecatedHidden(myFixture: JavaCodeInsightTestFixture) {
myFixture.configureByText(
// Example from KTIJ-18039
"MyClass.kt", """
@Deprecated(level = DeprecationLevel.WARNING, message="subject to change")
fun test1() { }
@Deprecated(level = DeprecationLevel.HIDDEN, message="no longer supported")
fun test2() { }
fun test2() = Test(22)
class Test(private val parameter: Int) {
@Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -69,20 +70,31 @@ interface UastApiFixtureTestBase : UastPluginSelection {
val test1 = uFile.findElementByTextFromPsi<UMethod>("test1", strict = false)
TestCase.assertNotNull("can't convert function test1", test1)
// KTIJ-18716
TestCase.assertTrue("Warning level, hasAnnotation", test1.javaPsi.hasAnnotation("kotlin.Deprecated"))
// KTIJ-18039
TestCase.assertTrue("Warning level, isDeprecated", test1.javaPsi.isDeprecated)
// KTIJ-18720
TestCase.assertTrue("Warning level, public", test1.javaPsi.hasModifierProperty(PsiModifier.PUBLIC))
// KTIJ-23807
TestCase.assertTrue("Warning level, nullability", test1.javaPsi.annotations.none { it.isNullnessAnnotation })
val test2 = uFile.findElementByTextFromPsi<UMethod>("test2", strict = false)
TestCase.assertNotNull("can't convert function test2", test2)
// KTIJ-18716
TestCase.assertTrue("Hidden level, hasAnnotation", test2.javaPsi.hasAnnotation("kotlin.Deprecated"))
// KTIJ-18039
TestCase.assertTrue("Hidden level, isDeprecated", test2.javaPsi.isDeprecated)
// KTIJ-18720
TestCase.assertTrue("Hidden level, public", test2.javaPsi.hasModifierProperty(PsiModifier.PUBLIC))
// KTIJ-23807
TestCase.assertNotNull("Hidden level, nullability", test2.javaPsi.annotations.singleOrNull { it.isNullnessAnnotation })
val testClass = uFile.findElementByTextFromPsi<UClass>("Test", strict = false)
TestCase.assertNotNull("can't convert class Test", testClass)
testClass.methods.forEach { mtd ->
if (mtd.sourcePsi is KtConstructor<*>) {
// KTIJ-20200
TestCase.assertTrue("$mtd should be marked as a constructor", mtd.isConstructor)
}
}
......
......@@ -842,11 +842,6 @@ interface UastResolveApiFixtureTestBase : UastPluginSelection {
)
}
private val PsiAnnotation.isNullnessAnnotation: Boolean
get() {
return qualifiedName?.endsWith("NotNull") == true || qualifiedName?.endsWith("Nullable") == true
}
fun checkArrayAccessOverloads(myFixture: JavaCodeInsightTestFixture) {
myFixture.addClass(
"""
......
......@@ -2,6 +2,7 @@
package org.jetbrains.uast.test.common.kotlin
import com.intellij.psi.PsiAnnotation
import com.intellij.util.PairProcessor
import com.intellij.util.ref.DebugReflectionUtil
import junit.framework.TestCase
......@@ -36,4 +37,9 @@ fun checkDescriptorsLeak(node: UElement) {
fun <T> T?.orFail(msg: String): T {
return this
?: throw AssertionError(msg)
}
\ No newline at end of file
}
internal val PsiAnnotation.isNullnessAnnotation: Boolean
get() {
return qualifiedName?.endsWith("NotNull") == true || qualifiedName?.endsWith("Nullable") == true
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment